File Coverage

blib/lib/Config/Model.pm
Criterion Covered Total %
statement 712 886 80.3
branch 247 392 63.0
condition 116 209 55.5
subroutine 67 74 90.5
pod 15 52 28.8
total 1157 1613 71.7


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model
3             #
4             # This software is Copyright (c) 2005-2022 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             package Config::Model 2.153; # TRIAL
11              
12 59     59   7930279 use 5.20.0;
  59         931  
13 59     59   334 use strict ;
  59         147  
  59         1359  
14 59     59   294 use warnings;
  59         151  
  59         2080  
15              
16 59     59   29629 use Mouse;
  59         1721100  
  59         274  
17 59     59   23932 use Mouse::Util::TypeConstraints;
  59         176  
  59         291  
18 59     59   33707 use MouseX::StrictConstructor;
  59         17916  
  59         238  
19              
20 59     59   9800 use Carp;
  59         137  
  59         4032  
21 59     59   41175 use Storable ('dclone');
  59         201544  
  59         3794  
22 59     59   25249 use Data::Dumper ();
  59         271311  
  59         1936  
23 59     59   44288 use Log::Log4perl 1.11 qw(get_logger :levels);
  59         2391388  
  59         403  
24 59     59   45889 use Config::Model::Instance;
  59         287  
  59         3099  
25 59     59   32948 use Hash::Merge 0.12 qw/merge/;
  59         601145  
  59         4431  
26 59     59   509 use Path::Tiny 0.053;
  59         1070  
  59         2948  
27 59     59   32973 use File::HomeDir;
  59         346898  
  59         3429  
28              
29 59     59   539 use Cwd;
  59         177  
  59         3687  
30 59     59   26654 use Config::Model::Lister;
  59         199  
  59         2729  
31              
32             with "Config::Model::Role::Constants";
33              
34 59     59   509 use parent qw/Exporter/;
  59         157  
  59         745  
35             our @EXPORT_OK = qw/cme initialize_log4perl/;
36              
37 59     59   5659 use feature qw/signatures postderef/;
  59         173  
  59         5874  
38 59     59   428 no warnings qw/experimental::signatures experimental::postderef/;
  59         169  
  59         910970  
39              
40             # used in some tests where we don't want to load
41             # ~/.log4config-model config
42             my $force_default_log;
43 1     1 0 120 sub force_usage_of_default_log_config () {
  1         2  
44 1         3 return $force_default_log = 1;
45             }
46              
47             my $legacy_logger = get_logger("Model::Legacy") ;
48             my $loader_logger = get_logger("Model::Loader") ;
49             my $logger = get_logger("Model") ;
50              
51             # used to keep one Config::Model object to simplify programs based on
52             # cme function
53             my $model_storage;
54              
55             enum LegacyTreament => qw/die warn ignore/;
56              
57             has skip_include => ( isa => 'Bool', is => 'ro', default => 0 );
58             has model_dir => ( isa => 'Str', is => 'ro', default => 'Config/Model/models' );
59             has legacy => ( isa => 'LegacyTreament', is => 'ro', default => 'warn' );
60             has instances => (
61             isa => 'HashRef[Config::Model::Instance]',
62             is => 'ro',
63             default => sub { {} },
64             traits => ['Hash'],
65             handles => {
66             store_instance => 'set',
67             get_instance => 'get',
68             has_instance => 'defined',
69             },
70             );
71              
72             # Config::Model stores 3 versions of each model
73              
74             # raw_model is the model exactly as passed by the user. Since the format is quite
75             # liberal (e.g legacy parameters, grouped declaration of elements like '[qw/foo bar/] => {}}',
76             # element description in class or in element declaration)), this raw format is not
77             # usable without normalization (done by normalize_class_parameters)
78              
79             # the key if this hash is a model name
80             has raw_models => (
81             isa => 'HashRef',
82             is => 'ro',
83             default => sub { {} },
84             traits => ['Hash'],
85             handles => {
86             raw_model_exists => 'exists',
87             raw_model_defined => 'defined',
88             raw_model => 'get',
89             get_raw_model => 'get',
90             store_raw_model => 'set',
91             raw_model_names => 'keys',
92             },
93             );
94              
95             # the result of normalization is stored here. Normalized model aggregate user models and
96             # augmented features (the one found in Foo.d directory). inclusion of other class is NOT
97             # yet done. normalized_models are created while loading files (load method) or creating
98             # configuration classes (create_config_class)
99             has normalized_models => (
100             isa => 'HashRef',
101             is => 'ro',
102             default => sub { {} },
103             traits => ['Hash'],
104             handles => {
105             normalized_model_exists => 'exists',
106             normalized_model_defined => 'defined',
107             normalized_model => 'get',
108             store_normalized_model => 'set',
109             normalized_model_names => 'keys',
110             },
111             );
112              
113             # This attribute contain the model that will be used by Config::Model::Node. They
114             # are created on demand when get_model is called. When created the inclusion of
115             # other classes is done according to the class 'include' parameter. Note that get_model
116             # will try to call load if the required normalized_model is not known (lazy loading)
117             has models => (
118             isa => 'HashRef',
119             is => 'ro',
120             default => sub { {} },
121             traits => ['Hash'],
122             handles => {
123             model_exists => 'exists',
124             model_defined => 'defined',
125             _get_model => 'get',
126             _store_model => 'set',
127             },
128             );
129              
130             # model snippet may be loaded when the target class is not available
131             # so they must be stored before being used.
132             has model_snippets => (
133             isa => 'ArrayRef',
134             is => 'ro',
135             default => sub { [] },
136             traits => ['Array'],
137             handles => {
138             add_snippet => 'push',
139             all_snippets => 'elements',
140             },
141             );
142              
143              
144             enum 'LOG_LEVELS', [ qw/ERROR WARN INFO DEBUG TRACE/ ];
145              
146             has log_level => (
147             isa => 'LOG_LEVELS',
148             is => 'ro',
149             );
150              
151             has skip_inheritance => (
152             isa => 'Bool',
153             is => 'ro',
154             default => 0,
155             trigger => sub {
156             my $self = shift;
157             $self->show_legacy_issue("skip_inheritance is deprecated, use skip_include");
158             $self->skip_include = $self->skip_inheritance;
159             } );
160              
161             # remove this hack mid 2022
162             around BUILDARGS => sub ($orig, $class, %args) {
163             my %new;
164             foreach my $k (keys %args) {
165             if (defined $args{$k}) {
166             $new{$k} = $args{$k};
167             }
168             else {
169             # cannot use logger, it's not initialised yet
170             croak("Config::Model new: passing undefined constructor argument is deprecated ($k argument)\n");
171             }
172             }
173              
174             return $class->$orig(%new);
175             };
176              
177             # keep this as a separate sub from BUILD. So user can call it before
178             # creating Config::Model object
179 41     41 1 8615 sub initialize_log4perl (@args) {
  41         195  
  41         84  
180 41 50       175 if (ref $args[0]) {
181             # may be called as $self-> initialize_log4perl
182 0         0 shift @args;
183             }
184              
185 41         188 my %args = @args;
186              
187 41         260 my $log4perl_syst_conf_file = path('/etc/log4config-model.conf');
188             # avoid undef warning when homedir is not defined (e.g. with Debian cowbuilder)
189 41   50     2970 my $home = File::HomeDir->my_home // '';
190 41         4622 my $log4perl_user_conf_file = path( $home . '/.log4config-model' );
191              
192 41         1744 my $fallback_conf_file = path($INC{"Config/Model.pm"})
193             ->parent->child("Model/log4perl.conf") ;
194              
195              
196 41 50       9075 my $log4perl_file =
    50          
    100          
197             $force_default_log ? $fallback_conf_file # for tests
198             : $log4perl_user_conf_file->is_file ? $log4perl_user_conf_file
199             : $log4perl_syst_conf_file->is_file ? $log4perl_syst_conf_file
200             : $fallback_conf_file;
201             my %log4perl_conf =
202 1107         3473 map { split /\s*=\s*/,$_,2; }
203 41         1554 grep { chomp; ! /^\s*#/ } $log4perl_file->lines;
  1353         15284  
  1353         2865  
204              
205 41         330 my $verbose = $args{verbose};
206 41 100       278 if (defined $verbose) {
207 2 100       13 my @loggers = ref $verbose ? @$verbose : $verbose;
208 2         8 foreach my $logger (@loggers) {
209 3         13 $log4perl_conf{"log4perl.logger.Verbose.$logger"} = "INFO, PlainMsgOnScreen";
210             }
211             }
212              
213 41         375 Log::Log4perl::init(\%log4perl_conf);
214              
215 41         1083321 return \%log4perl_conf; # for tests
216             }
217              
218             sub BUILD {
219 79     79 1 261 my $self = shift;
220 79         192 my $args = shift;
221 79 100       724 initialize_log4perl(verbose => $args->{verbose}) unless Log::Log4perl->initialized();
222 79         1037 return;
223             }
224              
225             sub show_legacy_issue {
226 1     1 0 4 my $self = shift;
227 1         3 my $ref = shift;
228 1   33     6 my $behavior = shift || $self->legacy;
229              
230 1 50       5 my @msg = ref $ref ? @$ref : $ref;
231 1         3 unshift @msg, "Model ";
232 1 50       12 if ( $behavior eq 'die' ) {
    50          
    0          
233 0         0 die @msg, "\n";
234             }
235             elsif ( $behavior eq 'warn' ) {
236 1         5 $legacy_logger->warn(@msg);
237             } elsif ( $behavior eq 'note' ) {
238 0         0 $legacy_logger->info( @msg);
239             }
240 1         12 return;
241             }
242              
243             sub _tweak_instance_args {
244 148     148   419 my ($args) = @_ ;
245              
246 148         488 my $application = $args->{application} ;
247 148         329 my $cat = '';
248 148 100       628 if (defined $application) {
249 46         222 my ( $categories, $appli_info, $appli_map ) = Config::Model::Lister::available_models;
250              
251             # root_class_name may override class found (or not) by appli in tests
252 46 100       194 if (not $args->{root_class_name}) {
253 5   50     30 $args->{root_class_name} = $appli_map->{$application} ||
254             die "Unknown application $application. Expected one of "
255             . join(' ',sort keys %$appli_map)."\n";
256             }
257              
258 46   50     254 $cat = $appli_info->{_category} // ''; # may be empty in tests
259             # config_dir may be specified in application file
260 46   33     266 $args->{config_dir} //= $appli_info->{$application}{config_dir};
261 46   50     419 $args->{appli_info} = $appli_info->{$application} // {};
262             }
263              
264 148         391 my $app_name = $application;
265 148 50       592 if ($cat eq 'application') {
266             # store dir in name to distinguish different runs of the same
267             # app in different directories.
268 0         0 $application .= " in " . cwd;
269             }
270             $args->{name}
271             = delete $args->{instance_name} # backward compat with test
272             || delete $args->{name} # preferred parameter
273 148   100     850 || $app_name # fallback in most cases
274             || 'default'; # fallback mostly in tests
275 148         381 return;
276             }
277              
278 5     5 1 37465 sub cme (@args) {
  5         21  
  5         11  
279 5 100       34 my %args = @args == 1 ? ( application => $args[0]) : @args ;
280              
281 5 100       52 if (my $force = delete $args{'force-load'}) {
282 1 50       10 $args{check} = 'no' if $force;
283             }
284              
285 5         20 my $cat =_tweak_instance_args(\%args);
286              
287 5   50     26 my $m_args = delete $args{model_args} // {} ; # used for tests
288             # model_storage is used to keep Config::Model object alive
289 5   66     96 $model_storage //= Config::Model->new(%$m_args);
290              
291 5         113 return $model_storage->instance(%args);
292             }
293              
294 143     143 1 758086 sub instance ($self, @args) {
  143         393  
  143         618  
  143         299  
295 143 50       1139 my %args = @args == 1 ? ( application => $args[0]) : @args ;
296              
297             # also creates a default name
298 143         822 _tweak_instance_args(\%args);
299              
300 143 100 66     1657 if ( $args{name} and $self->has_instance($args{name}) ) {
301 4         78 return $self->get_instance($args{name});
302             }
303              
304             croak "Model: can't create instance without application or root_class_name "
305 139 50       2812 unless $args{root_class_name};
306              
307 139 100       619 if ( defined $args{model_file} ) {
308 15         61 my $file = delete $args{model_file};
309 15         83 $self->load( $args{root_class_name}, $file );
310             }
311              
312 139         4144 my $i = Config::Model::Instance->new(
313             config_model => $self,
314             %args # for optional parameters like *directory
315             );
316              
317 139         5091 $self->store_instance($args{name}, $i);
318 139         7979 return $i;
319             }
320              
321             sub instance_names {
322 0     0 0 0 my $self = shift;
323 0         0 my @all = sort keys %{ $self->instances };
  0         0  
324 0         0 return @all;
325             }
326              
327             # unpacked model is:
328             # {
329             # element_list => [ ... ],
330             # element => { element_name => element_data (left as is) },
331             # class_description => <class description string>,
332             # include => 'class_name',
333             # include_after => 'element_name',
334             # }
335             # description, summary, level, status are moved
336             # into element description.
337              
338             my @legal_params_to_move = (
339             qw/read_config write_config rw_config/, # read/write stuff
340              
341             # this parameter is filled by class generated by a program. It may
342             # be used to avoid interactive edition of a generated model
343             'generated_by',
344             qw/class_description author copyright gist license include include_after include_backend class/
345             );
346              
347             my @other_legal_params = qw/ author element status description summary level accept/;
348              
349             # keep as external API. All internal call go through _store_model
350             # See comments around raw_models attribute for explanations
351 110     110 1 664216 sub create_config_class ($self, %raw_model) {
  110         245  
  110         384  
  110         181  
352             my $config_class_name = delete $raw_model{name}
353 110 50       503 or croak "create_config_class: no config class name";
354              
355 110         460 get_logger("Model")->info("Creating class $config_class_name");
356              
357 110 50       5330 if ( $self->model_exists($config_class_name) ) {
358 0         0 Config::Model::Exception::ModelDeclaration->throw(
359             error => "create_config_class: attempt to clobber $config_class_name"
360             . " config class name " );
361             }
362              
363 110         10003 $self->store_raw_model( $config_class_name, dclone( \%raw_model ) );
364              
365 110         5418 my $model = $self->normalize_class_parameters( $config_class_name, \%raw_model );
366              
367 109         582 $self->store_normalized_model( $config_class_name, $model );
368              
369 109         4906 return $config_class_name;
370             }
371              
372             sub merge_included_class {
373 259     259 0 730 my ( $self, $config_class_name ) = @_;
374              
375 259         921 my $normalized_model = $self->normalized_model($config_class_name);
376 259         20905 my $model = dclone $normalized_model ;
377              
378             # add included elements
379 259 50 33     2318 if ( $self->skip_include and defined $normalized_model->{include} ) {
380 0         0 my $inc = $normalized_model->{include};
381 0 0       0 $model->{include} = ref $inc ? $inc : [$inc];
382             $model->{include_after} = $normalized_model->{include_after}
383 0 0       0 if defined $normalized_model->{include_after};
384             }
385             else {
386             # include class in raw_copy, normalized_model is left as is
387 259         1319 $self->include_class( $config_class_name, $model );
388             }
389              
390             # add included backend
391 258 50 33     1239 if ( $self->skip_include and defined $normalized_model->{include_backend} ) {
392 0         0 my $inc = $normalized_model->{include_backend};
393 0 0       0 $model->{include_backend} = ref $inc ? $inc : [$inc];
394             }
395             else {
396             # include read/write config specifications in raw_copy,
397             # normalized_model is left as is
398 258         916 $self->include_backend( $config_class_name, $model );
399             }
400              
401 258         567 return $model;
402             }
403              
404             sub include_backend {
405 258     258 1 480 my $self = shift;
406 258   33     735 my $class_name = shift || croak "include_backend: undef includer";
407 258   50     673 my $target_model = shift || die "include_backend:: undefined target_model";
408              
409 258         552 my $included_classes = delete $target_model->{include_backend};
410 258 100       738 return () unless defined $included_classes;
411              
412 1         4 foreach my $included_class (@$included_classes) {
413             # takes care of recursive include, because get_model will perform
414             # includes (and normalization). Is already a dclone
415 1         4 my $included_model = $self->get_model_clone($included_class);
416              
417 1         34 foreach my $rw (qw/rw_config read_config write_config config_dir/) {
418 4 50 33     19 if ($target_model->{$rw} and $included_model->{$rw}) {
    100          
419 0         0 my $msg = "Included $rw from $included_class cannot clobber "
420             . "existing data in $class_name";
421 0         0 Config::Model::Exception::ModelDeclaration->throw( error => $msg );
422             }
423             elsif ($included_model->{$rw}) {
424 1         4 $target_model->{$rw} = $included_model->{$rw};
425             }
426             }
427             }
428 1         3 return;
429             }
430              
431             sub normalize_class_parameters {
432 390     390 0 743 my $self = shift;
433 390   50     1114 my $config_class_name = shift || die;
434 390   50     989 my $normalized_model = shift || die;
435              
436 390         731 my $model = {};
437              
438             # sanity check
439 390         937 my $raw_name = delete $normalized_model->{name};
440 390 50 66     1627 if ( defined $raw_name and $config_class_name ne $raw_name ) {
441 0         0 my $e = "internal: config_class_name $config_class_name ne model name $raw_name";
442 0         0 Config::Model::Exception::ModelDeclaration->throw( error => $e );
443             }
444              
445 390         736 my @element_list;
446              
447             # first construct the element list
448 390 100       636 my @compact_list = @{ $normalized_model->{element} || [] };
  390         1650  
449 390         1084 while (@compact_list) {
450 1265         2438 my ( $item, $info ) = splice @compact_list, 0, 2;
451              
452             # store the order of element as declared in 'element'
453 1265 100       3666 push @element_list, ref($item) ? @$item : ($item);
454             }
455              
456 390 50       1268 if ( defined $normalized_model->{inherit_after} ) {
457 0         0 $self->show_legacy_issue([ "Model $config_class_name: inherit_after is deprecated ",
458             "in favor of include_after" ]);
459 0         0 $normalized_model->{include_after} = delete $normalized_model->{inherit_after};
460             }
461 390 50       949 if ( defined $normalized_model->{inherit} ) {
462 0         0 $self->show_legacy_issue(
463             "Model $config_class_name: inherit is deprecated in favor of include");
464 0         0 $normalized_model->{include} = delete $normalized_model->{inherit};
465             }
466              
467 390         902 foreach my $info (@legal_params_to_move) {
468 5070 100       9613 next unless defined $normalized_model->{$info};
469 349         981 $model->{$info} = delete $normalized_model->{$info};
470             }
471              
472             # first deal with perl file and cds_file backend
473 390         1437 $self->translate_legacy_backend_info( $config_class_name, $model );
474              
475             # handle accept parameter
476 390         718 my @accept_list;
477             my %accept_hash;
478 390   100     1674 my $accept_info = delete $normalized_model->{'accept'} || [];
479 390         1072 while (@$accept_info) {
480 46         90 my $name_match = shift @$accept_info; # should be a regexp
481              
482             # handle legacy
483 46 50       133 if ( ref $name_match ) {
484 0 0       0 my $implicit = defined $name_match->{name_match} ? '' : 'implicit ';
485 0         0 unshift @$accept_info, $name_match; # put data back in list
486 0   0     0 $name_match = delete $name_match->{name_match} || '.*';
487 0         0 $logger->warn("class $config_class_name: name_match ($implicit$name_match)",
488             " in accept is deprecated");
489             }
490              
491 46         130 push @accept_list, $name_match;
492 46         159 $accept_hash{$name_match} = shift @$accept_info;
493             }
494              
495 390         1014 $model->{accept} = \%accept_hash;
496 390         803 $model->{accept_list} = \@accept_list;
497              
498             # check for duplicate in @element_list.
499 390         611 my %check_list;
500 390         805 foreach (@element_list) { $check_list{$_}++ };
  1650         3478  
501 390         1252 my @extra = grep { $check_list{$_} > 1 } keys %check_list;
  1650         3464  
502 390 50       1083 if (@extra) {
503 0         0 Config::Model::Exception::ModelDeclaration->throw(
504             error => "class $config_class_name: @extra element "
505             . "is declared more than once. Check the included parts" );
506             }
507              
508 390         1421 $self->handle_experience_permission( $config_class_name, $normalized_model );
509              
510             # element is handled first
511 390         796 foreach my $info_name (qw/element status description summary level/) {
512 1950         3961 my $raw_compact_info = delete $normalized_model->{$info_name};
513              
514 1950 100       3766 next unless defined $raw_compact_info;
515              
516 415 50       1165 Config::Model::Exception::ModelDeclaration->throw(
517             error => "Data for parameter $info_name of $config_class_name"
518             . " is not an array ref" )
519             unless ref($raw_compact_info) eq 'ARRAY';
520              
521 415         1038 my @raw_info = @$raw_compact_info;
522 415         1000 while (@raw_info) {
523 1352         3889 my ( $item, $info ) = splice @raw_info, 0, 2;
524 1352 100       3629 my @element_names = ref($item) ? @$item : ($item);
525              
526             # move element informations (handled first)
527 1352 100       3295 if ( $info_name eq 'element' ) {
    50          
528              
529             # warp can be found only in element item
530 1265         3724 $self->translate_legacy_info( $config_class_name, $element_names[0], $info );
531              
532 1265         3035 $self->handle_experience_permission( $config_class_name, $info );
533              
534             # copy in element data *after* legacy translation
535 1265         2406 foreach (@element_names) { $model->{element}{$_} = dclone($info); };
  1650         35016  
536             }
537              
538             # move some information into element declaration (without clobberring)
539             elsif ( $info_name =~ /description|level|summary|status/ ) {
540 87         216 foreach (@element_names) {
541             Config::Model::Exception::ModelDeclaration->throw(
542             error => "create class $config_class_name: '$info_name' "
543             . "declaration for non declared element '$_'" )
544 151 100       384 unless defined $model->{element}{$_};
545              
546 150   33     700 $model->{element}{$_}{$info_name} ||= $info;
547             }
548             }
549             else {
550 0         0 die "Unexpected element $item in $config_class_name model";
551             }
552              
553             }
554             }
555              
556             Config::Model::Exception::ModelDeclaration->throw(
557 389 50       1273 error => "create class $config_class_name: unexpected "
558             . "parameters '"
559             . join( ', ', sort keys %$normalized_model ) . "' "
560             . "Expected '"
561             . join( "', '", @legal_params_to_move, @other_legal_params )
562             . "'" )
563             if keys %$normalized_model;
564              
565 389         1022 $model->{element_list} = \@element_list;
566              
567 389         1398 return $model;
568             }
569              
570             sub handle_experience_permission {
571 1884     1884 0 3419 my ( $self, $config_class_name, $model ) = @_;
572              
573 1884 50       3832 if (delete $model->{permission}) {
574 0         0 die "$config_class_name: parameter permission is obsolete\n";
575             }
576 1884 50       3549 if (delete $model->{experience}) {
577 0         0 carp "experience parameter is deprecated";
578             }
579 1884         2812 return;
580             }
581              
582             sub translate_legacy_info {
583 1265     1265 0 1972 my $self = shift;
584 1265   50     2573 my $config_class_name = shift || die;
585 1265         1880 my $elt_name = shift;
586 1265         1834 my $info = shift;
587              
588 1265         3368 $self->translate_warped_node_info( $config_class_name, $elt_name, 'warped_node', $info );
589              
590             #translate legacy warp information
591 1265 100       2799 if ( defined $info->{warp} ) {
592 116         546 $self->translate_warp_info( $config_class_name, $elt_name, $info->{type}, $info->{warp} );
593             }
594              
595 1265         3477 $self->translate_cargo_info( $config_class_name, $elt_name, $info );
596              
597 1265 100 66     4192 if ( defined $info->{cargo}
      100        
598             && defined $info->{cargo}{type}
599             && $info->{cargo}{type} eq 'warped_node' ) {
600 2         8 $self->translate_warped_node_info( $config_class_name, $elt_name, 'warped_node', $info->{cargo} );
601             }
602              
603 1265 100 100     3292 if ( defined $info->{cargo}
604             and defined $info->{cargo}{warp} ) {
605             $self->translate_warp_info(
606             $config_class_name, $elt_name,
607             $info->{cargo}{type},
608 3         35 $info->{cargo}{warp} );
609             }
610              
611             # compute cannot be warped
612 1265 100       2544 if ( defined $info->{compute} ) {
613 32         91 $self->translate_compute_info( $config_class_name, $elt_name, $info, 'compute' );
614 32         75 $self->translate_allow_compute_override( $config_class_name, $elt_name, $info );
615             }
616 1265 100 100     3130 if ( defined $info->{cargo}
617             and defined $info->{cargo}{compute} ) {
618 2         17 $self->translate_compute_info( $config_class_name, $elt_name, $info->{cargo}, 'compute' );
619 2         6 $self->translate_allow_compute_override( $config_class_name, $elt_name, $info->{cargo} );
620             }
621              
622             # refer_to cannot be warped
623 1265 100       2490 if ( defined $info->{refer_to} ) {
624 55         293 $self->translate_compute_info( $config_class_name, $elt_name, $info,
625             refer_to => 'computed_refer_to' );
626             }
627 1265 100 100     3205 if ( defined $info->{cargo}
628             and defined $info->{cargo}{refer_to} ) {
629             $self->translate_compute_info( $config_class_name, $elt_name,
630 3         10 $info->{cargo}, refer_to => 'computed_refer_to' );
631             }
632              
633             # translate id default param
634             # default cannot be stored in cargo since is applies to the id itself
635 1265 100 100     6043 if ( defined $info->{type}
      100        
636             and ( $info->{type} eq 'list' or $info->{type} eq 'hash' ) ) {
637 320 50       792 if ( defined $info->{default} ) {
638 0         0 $self->translate_id_default_info( $config_class_name, $elt_name, $info );
639             }
640 320 50       760 if ( defined $info->{auto_create} ) {
641 0         0 $self->translate_id_auto_create( $config_class_name, $elt_name, $info );
642             }
643 320         1017 $self->translate_id_min_max( $config_class_name, $elt_name, $info );
644 320         1019 $self->translate_id_names( $config_class_name, $elt_name, $info );
645 320 100       836 if ( defined $info->{warp} ) {
646 6         17 my $rules_a = $info->{warp}{rules};
647 6         24 my %h = @$rules_a;
648 6         17 foreach my $rule_effect ( values %h ) {
649 12         31 $self->translate_id_names( $config_class_name, $elt_name, $rule_effect );
650 12         38 $self->translate_id_min_max( $config_class_name, $elt_name, $rule_effect );
651 12 50       47 next unless defined $rule_effect->{default};
652 0         0 $self->translate_id_default_info( $config_class_name, $elt_name, $rule_effect );
653             }
654             }
655 320         885 $self->translate_id_class($config_class_name, $elt_name, $info );
656             }
657              
658 1265 100 100     4544 if ( defined $info->{type} and ( $info->{type} eq 'leaf' ) ) {
659 695         1749 $self->translate_legacy_builtin( $config_class_name, $info, $info, );
660             }
661              
662 1265 100 100     4180 if ( defined $info->{type} and ( $info->{type} eq 'check_list' ) ) {
663 63         338 $self->translate_legacy_built_in_list( $config_class_name, $info, $info, );
664             }
665              
666             $legacy_logger->debug(
667 1265 50       2788 Data::Dumper->Dump( [$info], [ 'translated_' . $elt_name ] )
668             ) if $legacy_logger->is_debug;
669 1265         6787 return;
670             }
671              
672             sub translate_legacy_backend_info {
673 390     390 0 867 my ( $self, $config_class_name, $model ) = @_;
674              
675             # trap multi backend and change array spec into single spec
676 390         873 foreach my $config (qw/read_config write_config/) {
677 780         1345 my $ref = $model->{$config};
678 780 50 33     1946 if ($ref and ref($ref) eq 'ARRAY') {
679 0 0       0 if (@$ref == 1) {
    0          
680 0         0 $model->{$config} = $ref->[0];
681             }
682             elsif (@$ref > 1){
683 0         0 $self->show_legacy_issue("$config_class_name $config: multiple backends are obsolete. You now must use only one backend.", 'die');
684             }
685             }
686             }
687              
688             # move read_config spec in re_config
689 390 50       1066 if ($model->{read_config}) {
690 0         0 $self->show_legacy_issue("$config_class_name: read_config specification is deprecated, please move in rw_config", 'warn');
691 0         0 $model->{rw_config} = delete $model->{read_config};
692             }
693              
694             # merge write_config spec in rw_config
695 390 50       939 if ($model->{write_config}) {
696 0         0 $self->show_legacy_issue("$config_class_name: write_config specification is deprecated, please merge with read_config and move in rw_config", 'warn');
697 0         0 foreach (keys %{$model->{write_config}}) {
  0         0  
698 0         0 $model->{rw_config}{$_} = $model->{write_config}{$_}
699             }
700 0         0 delete $model->{write_config};
701             }
702              
703 390   100     1123 my $ref = $model->{'rw_config'} || return;
704              
705 105 50       297 die "undefined backend in rw_config spec of class $config_class_name\n" unless $ref->{backend} ;
706              
707 105 50       317 if ($ref->{backend} eq 'custom') {
708 0         0 my $msg = "$config_class_name: custom read/write backend is obsolete."
709             ." Please replace with a backend inheriting Config::Model::Backend::Any";
710 0         0 $self->show_legacy_issue( $msg, 'die');
711             }
712              
713 105 100       599 if ( $ref->{backend} =~ /^(perl|ini|cds)$/ ) {
714 1         5 my $backend = $ref->{backend};
715 1         8 $self->show_legacy_issue("$config_class_name: deprecated backend '$backend'. Should be '$ {backend}_file'", 'warn');
716 1         3 $ref->{backend} .= "_file";
717             }
718              
719 105 50       282 if ( defined $ref->{allow_empty} ) {
720 0         0 $self->show_legacy_issue("$config_class_name: backend $ref->{backend}: allow_empty is deprecated. Use auto_create", 'warn');
721 0         0 $ref->{auto_create} = delete $ref->{allow_empty};
722             }
723 105         194 return;
724             }
725              
726             sub translate_cargo_info {
727 1265     1265 0 1936 my $self = shift;
728 1265         1772 my $config_class_name = shift;
729 1265         1799 my $elt_name = shift;
730 1265         1661 my $info = shift;
731              
732 1265         2001 my $c_type = delete $info->{cargo_type};
733 1265 50       2768 return unless defined $c_type;
734 0         0 $self->show_legacy_issue("$config_class_name->$elt_name: parameter cargo_type is deprecated.");
735 0         0 my %cargo;
736              
737 0 0       0 if ( defined $info->{cargo_args} ) {
738 0         0 %cargo = %{ delete $info->{cargo_args} };
  0         0  
739 0         0 $self->show_legacy_issue(
740             "$config_class_name->$elt_name: parameter cargo_args is deprecated.");
741             }
742              
743 0         0 $cargo{type} = $c_type;
744              
745 0 0       0 if ( defined $info->{config_class_name} ) {
746 0         0 $cargo{config_class_name} = delete $info->{config_class_name};
747 0         0 $self->show_legacy_issue([
748             "$config_class_name->$elt_name: parameter config_class_name is ",
749             "deprecated. This one must be specified within cargo. ",
750             "Ie. cargo=>{config_class_name => 'FooBar'}"
751             ]);
752             }
753              
754 0         0 $info->{cargo} = \%cargo;
755 0 0       0 $legacy_logger->debug(
756             Data::Dumper->Dump( [$info], [ 'translated_' . $elt_name ] )
757             ) if $legacy_logger->is_debug;
758 0         0 return;
759             }
760              
761             sub translate_id_names {
762 332     332 0 551 my $self = shift;
763 332         514 my $config_class_name = shift;
764 332         512 my $elt_name = shift;
765 332         533 my $info = shift;
766 332         981 $self->translate_name( $config_class_name, $elt_name, $info, 'allow', 'allow_keys', 'die' );
767 332         886 $self->translate_name( $config_class_name, $elt_name, $info, 'allow_from', 'allow_keys_from', 'die' );
768 332         855 $self->translate_name( $config_class_name, $elt_name, $info, 'follow', 'follow_keys_from', 'die' );
769 332         509 return;
770             }
771              
772             sub translate_name {
773 996     996 0 2082 my ($self, $config_class_name, $elt_name, $info, $from, $to, $legacy) = @_;
774              
775 996 50       1985 if ( defined $info->{$from} ) {
776 0         0 $self->show_legacy_issue(
777             "$config_class_name->$elt_name: parameter $from is deprecated in favor of $to",
778             $legacy
779             );
780 0         0 $info->{$to} = delete $info->{$from};
781             }
782 996         1495 return;
783             }
784              
785             sub translate_allow_compute_override {
786 34     34 0 47 my $self = shift;
787 34         46 my $config_class_name = shift;
788 34         50 my $elt_name = shift;
789 34         45 my $info = shift;
790              
791 34 50       83 if ( defined $info->{allow_compute_override} ) {
792 0         0 $self->show_legacy_issue(
793             "$config_class_name->$elt_name: parameter allow_compute_override is deprecated in favor of compute -> allow_override"
794             );
795 0         0 $info->{compute}{allow_override} = delete $info->{allow_compute_override};
796             }
797 34         65 return;
798             }
799              
800             sub translate_compute_info {
801 92     92 0 183 my $self = shift;
802 92         162 my $config_class_name = shift;
803 92         186 my $elt_name = shift;
804 92         183 my $info = shift;
805 92         172 my $old_name = shift;
806 92   66     255 my $new_name = shift || $old_name;
807              
808 92 50       315 if ( ref( $info->{$old_name} ) eq 'ARRAY' ) {
809 0         0 my $compute_info = delete $info->{$old_name};
810 0 0       0 $legacy_logger->debug(
811             "translate_compute_info $elt_name input:\n",
812             Data::Dumper->Dump( [$compute_info], [qw/compute_info/] )
813             ) if $legacy_logger->is_debug;
814              
815 0         0 $self->show_legacy_issue([ "$config_class_name->$elt_name: specifying compute info with ",
816             "an array ref is deprecated" ]);
817              
818 0         0 my ( $user_formula, %var ) = @$compute_info;
819 0         0 my $replace_h;
820 0 0       0 foreach ( keys %var ) { $replace_h = delete $var{$_} if ref( $var{$_} ) };
  0         0  
821              
822             # cleanup user formula
823 0         0 $user_formula =~ s/\$(\w+)\{/\$replace{/g;
824              
825             # cleanup variable
826 0         0 foreach ( values %var ) { s/\$(\w+)\{/\$replace{/g };
  0         0  
827              
828             # change the hash *in* the info structure
829 0         0 $info->{$new_name} = {
830             formula => $user_formula,
831             variables => \%var,
832             };
833 0 0       0 $info->{$new_name}{replace} = $replace_h if defined $replace_h;
834              
835             $legacy_logger->debug(
836             "translate_warp_info $elt_name output:\n",
837 0 0       0 Data::Dumper->Dump( [ $info->{$new_name} ], [ 'new_' . $new_name ] )
838             ) if $legacy_logger->is_debug;
839             }
840 92         187 return;
841             }
842              
843             sub translate_id_class {
844 320     320 0 557 my $self = shift;
845 320   50     707 my $config_class_name = shift || die;
846 320         534 my $elt_name = shift;
847 320         462 my $info = shift;
848              
849              
850 320 50       814 $legacy_logger->debug(
851             "translate_id_class $elt_name input:\n",
852             Data::Dumper->Dump( [$info], [qw/info/] )
853             ) if $legacy_logger->is_debug;
854              
855 320         2206 my $class_overide_param = $info->{type}.'_class';
856 320         608 my $class_overide = $info->{$class_overide_param};
857 320 50       708 if ($class_overide) {
858 0         0 $info->{class} = $class_overide;
859 0         0 $self->show_legacy_issue([
860             "$config_class_name->$elt_name: '$class_overide_param' is deprecated, ",
861             "Use 'class' instead."
862             ]);
863             }
864              
865             $legacy_logger->debug(
866 320 50       748 "translate_id_class $elt_name output:",
867             Data::Dumper->Dump( [$info], [qw/new_info/])
868             ) if $legacy_logger->is_debug;
869 320         1805 return;
870             }
871              
872             # internal: translate default information for id element
873             sub translate_id_default_info {
874 0     0 0 0 my $self = shift;
875 0   0     0 my $config_class_name = shift || die;
876 0         0 my $elt_name = shift;
877 0         0 my $info = shift;
878              
879 0 0       0 $legacy_logger->debug(
880             "translate_id_default_info $elt_name input:\n",
881             Data::Dumper->Dump( [$info], [qw/info/] )
882             ) if $legacy_logger->is_debug;
883              
884 0         0 my $warn = "$config_class_name->$elt_name: 'default' parameter for list or "
885             . "hash element is deprecated. ";
886              
887 0         0 my $def_info = delete $info->{default};
888 0 0       0 if ( ref($def_info) eq 'HASH' ) {
    0          
889 0         0 $info->{default_with_init} = $def_info;
890 0         0 $self->show_legacy_issue([ $warn, "Use default_with_init" ]);
891             }
892             elsif ( ref($def_info) eq 'ARRAY' ) {
893 0         0 $info->{default_keys} = $def_info;
894 0         0 $self->show_legacy_issue([ $warn, "Use default_keys" ]);
895             }
896             else {
897 0         0 $info->{default_keys} = [$def_info];
898 0         0 $self->show_legacy_issue([ $warn, "Use default_keys" ]);
899             }
900              
901 0 0       0 $legacy_logger->debug(
902             "translate_id_default_info $elt_name output:",
903             Data::Dumper->Dump( [$info], [qw/new_info/])
904             ) if $legacy_logger->is_debug;
905 0         0 return;
906             }
907              
908             # internal: translate auto_create information for id element
909             sub translate_id_auto_create {
910 0     0 0 0 my $self = shift;
911 0   0     0 my $config_class_name = shift || die;
912 0         0 my $elt_name = shift;
913 0         0 my $info = shift;
914              
915 0 0       0 $legacy_logger->debug(
916             "translate_id_auto_create $elt_name input:",
917             Data::Dumper->Dump( [$info], [qw/info/] )
918             ) if $legacy_logger->is_debug;
919              
920 0         0 my $warn = "$config_class_name->$elt_name: 'auto_create' parameter for list or "
921             . "hash element is deprecated. ";
922              
923 0         0 my $ac_info = delete $info->{auto_create};
924 0 0       0 if ( $info->{type} eq 'hash' ) {
    0          
925             $info->{auto_create_keys} =
926 0 0       0 ref($ac_info) eq 'ARRAY' ? $ac_info : [$ac_info];
927 0         0 $self->show_legacy_issue([ $warn, "Use auto_create_keys" ]);
928             }
929             elsif ( $info->{type} eq 'list' ) {
930 0         0 $info->{auto_create_ids} = $ac_info;
931 0         0 $self->show_legacy_issue([ $warn, "Use auto_create_ids" ]);
932             }
933             else {
934 0         0 die "Unexpected element ($elt_name) type $info->{type} ", "for translate_id_auto_create";
935             }
936              
937 0 0       0 $legacy_logger->debug(
938             "translate_id_default_info $elt_name output:\n",
939             Data::Dumper->Dump( [$info], [qw/new_info/] )
940             ) if $legacy_logger->is_debug;
941 0         0 return;
942             }
943              
944             sub translate_id_min_max {
945 332     332 0 591 my $self = shift;
946 332   50     747 my $config_class_name = shift || die;
947 332         511 my $elt_name = shift;
948 332         540 my $info = shift;
949              
950 332         602 foreach my $bad (qw/min max/) {
951 664 100       1547 next unless defined $info->{$bad};
952              
953 13 50       37 $legacy_logger->debug( "translate_id_min_max $elt_name $bad:")
954             if $legacy_logger->is_debug;
955              
956 13         81 my $good = $bad . '_index';
957 13         47 my $warn = "$config_class_name->$elt_name: '$bad' parameter for list or "
958             . "hash element is deprecated. Use '$good'";
959              
960 13         38 $info->{$good} = delete $info->{$bad};
961             }
962 332         614 return;
963             }
964              
965             sub translate_warped_node_info {
966 1267     1267 0 2523 my ( $self, $config_class_name, $elt_name, $type, $info ) = @_;
967              
968 1267 50       3380 $legacy_logger->debug(
969             "translate_warped_node_info $elt_name input:\n",
970             Data::Dumper->Dump( [$info], [qw/info/] )
971             ) if $legacy_logger->is_debug;
972              
973             # type may not be defined when translating class snippet used to augment a class
974 1267         7986 my $elt_type = $info->{type} ;
975 1267         2265 foreach my $parm (qw/follow rules/) {
976 2534 50       5489 next unless $info->{$parm};
977 0 0 0     0 next if defined $elt_type and $elt_type ne 'warped_node';
978 0         0 $self->show_legacy_issue(
979             "$config_class_name->$elt_name: using $parm parameter in "
980             ."warped node is deprecated. $parm must be specified in a warp parameter."
981             );
982 0         0 $info->{warp}{$parm} = delete $info->{$parm};
983             }
984              
985             $legacy_logger->debug(
986 1267 50       2638 "translate_warped_node_info $elt_name output:\n",
987             Data::Dumper->Dump( [$info], [qw/new_info/] )
988             ) if $legacy_logger->is_debug;
989 1267         6671 return;
990             }
991              
992             # internal: translate warp information into 'boolean expr' => { ... }
993             sub translate_warp_info {
994 119     119 0 337 my ( $self, $config_class_name, $elt_name, $type, $warp_info ) = @_;
995              
996 119 50       320 $legacy_logger->debug(
997             "translate_warp_info $elt_name input:\n",
998             Data::Dumper->Dump( [$warp_info], [qw/warp_info/] )
999             ) if $legacy_logger->is_debug;
1000              
1001 119         879 my $follow = $self->translate_follow_arg( $config_class_name, $elt_name, $warp_info->{follow} );
1002              
1003             # now, follow is only { w1 => 'warp1', w2 => 'warp2'}
1004 119         448 my @warper_items = values %$follow;
1005              
1006 119 100       334 my $multi_follow = @warper_items > 1 ? 1 : 0;
1007              
1008             my $rules =
1009             $self->translate_rules_arg( $config_class_name, $elt_name, $type, \@warper_items,
1010 119         440 $warp_info->{rules} );
1011              
1012 119         290 $warp_info->{follow} = $follow;
1013 119         283 $warp_info->{rules} = $rules;
1014              
1015 119 50       350 $legacy_logger->debug(
1016             "translate_warp_info $elt_name output:\n",
1017             Data::Dumper->Dump( [$warp_info], [qw/new_warp_info/] )
1018             ) if $legacy_logger->is_debug;
1019 119         784 return;
1020             }
1021              
1022             # internal
1023             sub translate_multi_follow_legacy_rules {
1024 12     12 0 33 my ( $self, $config_class_name, $elt_name, $warper_items, $raw_rules ) = @_;
1025 12         21 my @rules;
1026              
1027             # we have more than one warper_items
1028              
1029 12         57 for ( my $r_idx = 0 ; $r_idx < $#$raw_rules ; $r_idx += 2 ) {
1030 31         56 my $key_set = $raw_rules->[$r_idx];
1031 31 100       89 my @keys = ref($key_set) ? @$key_set : ($key_set);
1032              
1033             # legacy: check the number of keys in the @rules set
1034 31 50 66     139 if ( @keys != @$warper_items and $key_set !~ /\$\w+/ ) {
1035 0         0 Config::Model::Exception::ModelDeclaration->throw( error => "Warp rule error in "
1036             . "'$config_class_name->$elt_name'"
1037             . ": Wrong nb of keys in set '@keys',"
1038             . " Expected "
1039             . scalar @$warper_items
1040             . " keys" );
1041             }
1042              
1043             # legacy:
1044             # if a key of a rule (e.g. f1 or b1) is an array ref, all the
1045             # values passed in the array are considered as valid.
1046             # i.e. [ [ f1a, f1b] , b1 ] => { ... }
1047             # is equivalent to
1048             # [ f1a, b1 ] => { ... }, [ f1b , b1 ] => { ... }
1049              
1050             # now translate [ [ f1a, f1b] , b1 ] => { ... }
1051             # into "( $f1 eq f1a or $f1 eq f1b ) and $f2 eq b1)" => { ... }
1052 31         53 my @bool_expr;
1053 31         46 my $b_idx = 0;
1054 31         62 foreach my $key (@keys) {
1055 67 100       153 if ( ref $key ) {
    100          
1056 1         15 my @expr = map { "\$f$b_idx eq '$_'" } @$key;
  2         9  
1057 1         4 push @bool_expr, "(" . join( " or ", @expr ) . ")";
1058             }
1059             elsif ( $key !~ /\$\w+/ ) {
1060 57         120 push @bool_expr, "\$f$b_idx eq '$key'";
1061             }
1062             else {
1063 9         24 push @bool_expr, $key;
1064             }
1065 67         105 $b_idx++;
1066             }
1067 31         190 push @rules, join( ' and ', @bool_expr ), $raw_rules->[ $r_idx + 1 ];
1068             }
1069 12         57 return @rules;
1070             }
1071              
1072             sub translate_follow_arg {
1073 119     119 0 242 my $self = shift;
1074 119         205 my $config_class_name = shift;
1075 119         195 my $elt_name = shift;
1076 119         208 my $raw_follow = shift;
1077              
1078 119 100       498 if ( ref($raw_follow) eq 'HASH' ) {
    100          
    100          
1079              
1080             # follow is { w1 => 'warp1', w2 => 'warp2'}
1081 21         517 return $raw_follow;
1082             }
1083             elsif ( ref($raw_follow) eq 'ARRAY' ) {
1084              
1085             # translate legacy follow arguments ['warp1','warp2',...]
1086 5         13 my $follow = {};
1087 5         14 my $idx = 0;
1088 5         16 foreach ( @$raw_follow ) { $follow->{ 'f' . $idx++ } = $_ } ;
  12         41  
1089 5         14 return $follow;
1090             }
1091             elsif ( defined $raw_follow ) {
1092              
1093             # follow is a plain string
1094 90         356 return { f1 => $raw_follow };
1095             }
1096             else {
1097 3         14 return {};
1098             }
1099             }
1100              
1101             sub translate_rules_arg {
1102 119     119 0 339 my ( $self, $config_class_name, $elt_name, $type, $warper_items, $raw_rules ) = @_;
1103              
1104 119 100       318 my $multi_follow = @$warper_items > 1 ? 1 : 0;
1105 119         209 my $follow = @$warper_items;
1106              
1107             # $rules is either:
1108             # { f1 => { ... } } ( may be [ f1 => { ... } ] ?? )
1109             # [ 'boolean expr' => { ... } ]
1110             # legacy:
1111             # [ f1, b1 ] => {..} ,[ f1,b2 ] => {...}, [f2,b1] => {...} ...
1112             # foo => {...} , bar => {...}
1113 119         184 my @rules;
1114 119 100       444 if ( ref($raw_rules) eq 'HASH' ) {
    50          
    0          
1115              
1116             # transform the hash { foo => { ...} }
1117             # into array ref [ '$f1 eq foo' => { ... } ]
1118 35         93 my $h = $raw_rules;
1119 35 50       177 @rules = $follow ? map { ( "\$f1 eq '$_'", $h->{$_} ) } keys %$h : keys %$h;
  47         257  
1120             }
1121             elsif ( ref($raw_rules) eq 'ARRAY' ) {
1122 84 100       538 if ($multi_follow) {
1123 12         56 push @rules,
1124             $self->translate_multi_follow_legacy_rules( $config_class_name, $elt_name,
1125             $warper_items, $raw_rules );
1126             }
1127             else {
1128             # now translate [ f1a, f1b] => { ... }
1129             # into "$f1 eq f1a or $f1 eq f1b " => { ... }
1130 72         159 my @raw_rules = @{$raw_rules};
  72         216  
1131 72         292 for ( my $r_idx = 0 ; $r_idx < $#raw_rules ; $r_idx += 2 ) {
1132 152         310 my $key_set = $raw_rules[$r_idx];
1133 152 100       350 my @keys = ref($key_set) ? @$key_set : ($key_set);
1134 152 100       366 my @bool_expr = $follow ? map { /\$/ ? $_ : "\$f1 eq '$_'" } @keys : @keys;
  151 100       688  
1135 152         794 push @rules, join( ' or ', @bool_expr ), $raw_rules[ $r_idx + 1 ];
1136             }
1137             }
1138             }
1139             elsif ( defined $raw_rules ) {
1140 0         0 Config::Model::Exception::ModelDeclaration->throw(
1141             error => "Warp rule error in element "
1142             . "'$config_class_name->$elt_name': "
1143             . "rules must be a hash ref. Got '$raw_rules'" );
1144             }
1145              
1146 119         429 for ( my $idx = 1 ; $idx < @rules ; $idx += 2 ) {
1147 230 100       597 next unless ( ref $rules[$idx] eq 'HASH' ); # other cases are illegal and trapped later
1148 229         634 $self->handle_experience_permission( $config_class_name, $rules[$idx] );
1149 229 100 100     1056 next unless defined $type and $type eq 'leaf';
1150 89         315 $self->translate_legacy_builtin( $config_class_name, $rules[$idx], $rules[$idx] );
1151             }
1152              
1153 119         369 return \@rules;
1154             }
1155              
1156             sub translate_legacy_builtin {
1157 784     784 0 1635 my ( $self, $config_class_name, $model, $normalized_model ) = @_;
1158              
1159 784         1355 my $raw_builtin_default = delete $normalized_model->{built_in};
1160 784 50       1916 return unless defined $raw_builtin_default;
1161              
1162 0 0       0 $legacy_logger->debug(
1163             Data::Dumper->Dump( [$normalized_model], ['builtin to translate'] )
1164             ) if $legacy_logger->is_debug;
1165              
1166 0         0 $self->show_legacy_issue([ "$config_class_name: parameter 'built_in' is deprecated "
1167             . "in favor of 'upstream_default'" ]);
1168              
1169 0         0 $model->{upstream_default} = $raw_builtin_default;
1170              
1171 0 0       0 $legacy_logger->debug( Data::Dumper->Dump( [$model], ['translated_builtin'] ))
1172             if $legacy_logger->is_debug;
1173 0         0 return;
1174             }
1175              
1176             sub translate_legacy_built_in_list {
1177 63     63 0 220 my ( $self, $config_class_name, $model, $normalized_model ) = @_;
1178              
1179 63         162 my $raw_builtin_default = delete $normalized_model->{built_in_list};
1180 63 50       228 return unless defined $raw_builtin_default;
1181              
1182 0 0       0 $legacy_logger->debug(
1183             Data::Dumper->Dump( [$normalized_model], ['built_in_list to translate'] )
1184             ) if $legacy_logger->is_debug;
1185              
1186 0         0 $self->show_legacy_issue([ "$config_class_name: parameter 'built_in_list' is deprecated "
1187             . "in favor of 'upstream_default_list'" ]);
1188              
1189 0         0 $model->{upstream_default_list} = $raw_builtin_default;
1190              
1191 0 0       0 $legacy_logger->debug( Data::Dumper->Dump( [$model], ['translated_built_in_list'] ))
1192             if $legacy_logger->is_debug;
1193 0         0 return;
1194             }
1195              
1196             sub include_class {
1197 259     259 0 551 my $self = shift;
1198 259   33     821 my $class_name = shift || croak "include_class: undef includer";
1199 259   50     779 my $target_model = shift || die "include_class: undefined target_model";
1200              
1201 259         662 my $include_class = delete $target_model->{include};
1202              
1203 259 100       897 return () unless defined $include_class;
1204              
1205 64         403 my $include_after = delete $target_model->{include_after};
1206              
1207 64 100       283 my @includes = ref $include_class ? @$include_class : ($include_class);
1208              
1209             # use reverse because included classes are *inserted* in front
1210             # of the list (or inserted after $include_after
1211 64         178 foreach my $inc ( reverse @includes ) {
1212 65         394 $self->include_one_class( $class_name, $target_model, $inc, $include_after );
1213             }
1214 63         169 return;
1215             }
1216              
1217             sub include_one_class {
1218 65     65 0 135 my $self = shift;
1219 65   33     340 my $class_name = shift || croak "include_class: undef includer";
1220 65   33     180 my $target_model = shift || croak "include_class: undefined target_model";
1221 65   33     171 my $include_class = shift || croak "include_class: undef include_class param";
1222 65         131 my $include_after = shift;
1223              
1224 65         272 get_logger('Model')->debug("class $class_name includes $include_class");
1225              
1226 65 50 33     3746 if ( defined $include_class
1227             and defined $self->{included_class}{$class_name}{$include_class} ) {
1228 0         0 Config::Model::Exception::ModelDeclaration->throw(
1229             error => "Recursion error ? $include_class has "
1230             . "already been included by $class_name." );
1231             }
1232 65         235 $self->{included_class}{$class_name}{$include_class} = 1;
1233              
1234             # takes care of recursive include, because get_model will perform
1235             # includes (and normalization). Is already a dclone
1236 65         270 my $included_model = $self->get_model_clone($include_class);
1237              
1238             # now include element in element_list (special treatment because order is
1239             # important)
1240 65         3399 my $target_list = $target_model->{element_list};
1241 65         182 my $included_list = $included_model->{element_list};
1242 65         146 my $splice_idx = 0;
1243 65 100 66     338 if ( defined $include_after and defined $included_model->{element} ) {
1244 24         73 my $idx = 0;
1245 24         105 my %elt_idx = map { ( $_, $idx++ ); } @$target_list;
  57         222  
1246              
1247 24 50       124 if ( not defined $elt_idx{$include_after} ) {
1248 0         0 my $msg =
1249             "Unknown element for 'include_after': "
1250             . "$include_after, expected "
1251             . join( ' ', sort keys %elt_idx );
1252 0         0 Config::Model::Exception::ModelDeclaration->throw( error => $msg );
1253             }
1254              
1255             # + 1 because we splice *after* $include_after
1256 24         85 $splice_idx = $elt_idx{$include_after} + 1;
1257             }
1258              
1259 65         253 splice( @$target_list, $splice_idx, 0, @$included_list );
1260 65         291 get_logger('Model')->debug("class $class_name new elt list: @$target_list");
1261              
1262             # now actually include all elements
1263 65   100     3076 my $target_element = $target_model->{element} ||= {};
1264 65         210 foreach my $included_elt (@$included_list) {
1265 111 100       309 if ( not defined $target_element->{$included_elt} ) {
1266 110         277 get_logger('Model')->debug("class $class_name includes elt $included_elt");
1267 110         3667 $target_element->{$included_elt} = $included_model->{element}{$included_elt};
1268             }
1269             else {
1270 1         30 Config::Model::Exception::ModelDeclaration->throw(
1271             error => "Cannot clobber element '$included_elt' in $class_name"
1272             . " (included from $include_class)" );
1273             }
1274             }
1275 64         195 get_logger('Model')->debug("class $class_name include $include_class done");
1276 64         2300 return;
1277             }
1278              
1279 0     0 0 0 sub find_model_file_in_dir ($model_name, $model_path) {
  0         0  
  0         0  
  0         0  
1280 0         0 foreach my $ext (qw/yml yaml pl/) {
1281 0         0 my $sub_path = $model_name =~ s!::!/!rg;
1282 0         0 my $path_load_file = $model_path->child($sub_path . '.' . $ext);
1283 0 0       0 return $path_load_file if $path_load_file->exists;
1284             }
1285 0         0 return;
1286             }
1287              
1288             sub find_model_file_in_inc {
1289 50     50 0 175 my ($self, $model_name, $load_file) = @_;
1290              
1291 50         108 my $path_load_file ;
1292              
1293 50 100 100     820 if ($load_file and $load_file =~ m!^/! ) {
    50 33        
1294             # load_file is absolute, do not search in @INC
1295 1         12 $path_load_file = $load_file;
1296             } elsif ($self->model_dir and $self->model_dir =~ m!^/!) {
1297             # model_dir is absolute, do not search in @INC
1298 0         0 my $model_path = path($self->model_dir);
1299 0         0 $path_load_file = find_model_file_in_dir ($model_name, $model_path);
1300 0 0       0 Config::Model::Exception::ModelDeclaration->throw(
1301             error => "Cannot find $model_name file in $model_path"
1302             ) unless $path_load_file;
1303             }
1304             else {
1305 49         184 foreach my $inc_str (@INC) {
1306 75         657 my $inc_path = path($inc_str);
1307 75 100       3410 if ($load_file) {
1308 19         102 $path_load_file = $inc_path->child($load_file);
1309             }
1310             else {
1311 56         225 my $sub_path = $model_name =~ s!::!/!rg;
1312 56         291 my $model_path = $inc_path->child($self->model_dir);
1313 56         2338 foreach my $ext (qw/yml yaml pl/) {
1314 168         3218 $path_load_file = $model_path->child($sub_path . '.' . $ext);
1315 168 100       6640 last if $path_load_file->exists;
1316             }
1317             }
1318 75 100       2567 last if $path_load_file->exists;
1319             }
1320             }
1321              
1322 50 50       1609 Config::Model::Exception::ModelDeclaration->throw(
1323             error => "Cannot find $model_name file in \@INC"
1324             ) unless $path_load_file;
1325              
1326 50         395 $loader_logger->debug("model $model_name from file $path_load_file");
1327              
1328 50         881 return $path_load_file;
1329             }
1330              
1331             sub load_model_plugins {
1332 49     49 0 216 my ($self, @model_names) = @_;
1333              
1334             # look for additional model information
1335 49         143 my %model_graft_by_name;
1336             my %done; # avoid loading twice the same snippet (where system version may clobber dev version)
1337              
1338 49         194 foreach my $inc_str (@INC) {
1339 528         10139 foreach my $name ( @model_names ) {
1340 3003         46565 my $snippet_path = $name;
1341 3003         6760 $snippet_path =~ s/::/\//g;
1342 3003         7914 my $snippet_dir = path($inc_str)->absolute->child($self->model_dir)->child($snippet_path . '.d');
1343 3003         405439 $loader_logger->trace("looking for snippet in $snippet_dir");
1344 3003 100       37128 if ( $snippet_dir->is_dir ) {
1345 1         29 my $iter = $snippet_dir->iterator({ recurse => 1 });
1346              
1347 1         37 while ( my $snippet_file = $iter->() ) {
1348 1 50       198 next unless $snippet_file =~ /\.pl$/;
1349              
1350             # $snippet_file (Path::Tiny object) was
1351             # constructed from @INC content (i.e. $inc_str)
1352             # and contains an absolute path. Since
1353             # _load_model_in_hash uses 'do' (which may search
1354             # in @INC), the file path passed to
1355             # _load_model_in_hash must be either absolute or
1356             # relative to $inc_str
1357 1         16 my $snippet_file_rel = $snippet_file->relative($inc_str);
1358              
1359 1         412 my $done_key = $name . ':' . $snippet_file_rel;
1360 1 50       9 next if $done{$done_key};
1361 1         4 $loader_logger->info("Found snippet $snippet_file in $inc_str dir");
1362 1         18 my $snippet_model = $self->_load_model_file($snippet_file);
1363              
1364 1         6 $self->_merge_model_in_hash( \%model_graft_by_name, $snippet_model, $snippet_file_rel);
1365 1         8 $done{$done_key} = 1;
1366             }
1367             }
1368             }
1369             }
1370 49         1215 return %model_graft_by_name;
1371             }
1372              
1373             # load a model from file. See comments around raw_models attribute for explanations
1374             sub load {
1375 50     50 1 111340 my $self = shift;
1376 50         128 my $model_name = shift; # model name like Foo::Bar
1377 50         127 my $load_file = shift; # model file (override model name), used for tests
1378              
1379 50         443 $loader_logger->debug("called on model $model_name");
1380 50         733 my $path_load_file = $self->find_model_file_in_inc($model_name, $load_file);
1381              
1382 50         167 my %models_by_name;
1383              
1384             # Searches $load_file in @INC and returns an array containing the
1385             # names of the loaded classes
1386 50         290 my $model = $self->_load_model_file($path_load_file->absolute);
1387 49         371 my @loaded_classes = $self->_merge_model_in_hash( \%models_by_name, $model, $path_load_file );
1388              
1389 49         9392 $self->store_raw_model( $model_name, dclone( \%models_by_name ) );
1390              
1391 49         3105 foreach my $name ( keys %models_by_name ) {
1392 274         10673 my $data = $self->normalize_class_parameters( $name, $models_by_name{$name} );
1393 274         1351 $loader_logger->debug("Store normalized model $name");
1394 274         2856 $self->store_normalized_model( $name, $data );
1395             }
1396              
1397 49         2792 my %model_graft_by_name = $self->load_model_plugins(sort keys %models_by_name);
1398              
1399             # store snippet. May be used later
1400 49         277 foreach my $name (keys %model_graft_by_name) {
1401             # store snippet for later usage
1402 1         8 $loader_logger->trace("storing snippet for model $name");
1403 1         17 $self->add_snippet($model_graft_by_name{$name});
1404             }
1405              
1406             # check if a snippet is available for this class
1407 49         376 foreach my $snippet ( $self->all_snippets ) {
1408 2         23 my $class_to_merge = $snippet->{name};
1409 2 100       7 next unless $models_by_name{$class_to_merge};
1410 1         11 $self->augment_config_class_really( $class_to_merge, $snippet );
1411             }
1412              
1413             # return the list of classes found in $load_file. Respecting the order of the class
1414             # declaration is important for Config::Model::Itself so the class are written back
1415             # in the same order.
1416 49         1764 return @loaded_classes;
1417             }
1418              
1419             # New subroutine "_load_model_in_hash" extracted - Fri Apr 12 17:29:56 2013.
1420             #
1421             sub _merge_model_in_hash {
1422 50     50   247 my ( $self, $hash_ref, $model, $load_file ) = @_;
1423              
1424 50         111 my @names;
1425 50         208 foreach my $config_class_info (@$model) {
1426 275 50       1471 my %data =
    100          
1427             ref $config_class_info eq 'HASH' ? %$config_class_info
1428             : ref $config_class_info eq 'ARRAY' ? @$config_class_info
1429             : croak "load $load_file: config_class_info is not a ref";
1430             my $config_class_name = $data{name}
1431 275 50       805 or croak "load: missing config class name in $load_file";
1432              
1433             # check config class parameters and fill %model
1434 275         751 $hash_ref->{$config_class_name} = \%data;
1435 275         674 push @names, $config_class_name;
1436             }
1437              
1438 50         285 return @names;
1439             }
1440              
1441             sub _load_model_file {
1442 51     51   4536 my ( $self, $load_file ) = @_;
1443              
1444 51         281 $loader_logger->info("load model $load_file");
1445              
1446 51         852 my $err_msg = '';
1447             # do searches @INC if the file path is not absolute
1448 51         196 my $model = do $load_file;
1449              
1450 51 100 66     34933 unless ($model) {
1451 1 50       18 if ($@) { $err_msg = "couldn't parse $load_file: $@"; }
  0 50       0  
1452 1         5 elsif ( not defined $model ) { $err_msg = "couldn't do $load_file: $!" }
1453 0         0 else { $err_msg = "couldn't run $load_file"; }
1454             }
1455             elsif ( ref($model) ne 'ARRAY' ) {
1456             $model = [$model];
1457             }
1458              
1459 51 100       281 Config::Model::Exception::ModelDeclaration->throw( message => "load error: $err_msg" )
1460             if $err_msg;
1461              
1462 50         186 return $model;
1463             }
1464              
1465             sub augment_config_class {
1466 5     5 1 1738 my ( $self, %augment_data ) = @_;
1467              
1468             # %args must contain existing class name to augment
1469              
1470             # plus other data to merge to raw model
1471             my $config_class_name = delete $augment_data{name}
1472 5   33     58 || croak "augment_config_class: missing class name";
1473              
1474 5         31 $self->augment_config_class_really( $config_class_name, \%augment_data );
1475 5         19 return;
1476             }
1477              
1478             sub augment_config_class_really {
1479 6     6 0 20 my ( $self, $config_class_name, $augment_data ) = @_;
1480              
1481 6         31 my $orig_model = $self->normalized_model($config_class_name);
1482 6 50       121 croak "unknown class to augment: $config_class_name" unless defined $orig_model;
1483              
1484 6         317 my $model_addendum = $self->normalize_class_parameters( $config_class_name, dclone($augment_data) );
1485              
1486 6         68 my $merge = Hash::Merge->new('RIGHT_PRECEDENT');
1487 6         701 my $new_model = $merge->merge( $orig_model, $model_addendum );
1488              
1489             # remove duplicates in element_list and accept_list while keeping order
1490 6         4134 foreach my $list_name (qw/element_list accept_list/) {
1491 12         29 my %seen;
1492             my @newlist;
1493 12         22 foreach my $elt ( @{ $new_model->{$list_name} } ) {
  12         32  
1494 83 100       182 push @newlist, $elt unless $seen{$elt};
1495 83         131 $seen{$elt} = 1;
1496             }
1497              
1498 12         52 $new_model->{$list_name} = \@newlist;
1499             }
1500              
1501 6         36 $self->store_normalized_model( $config_class_name => $new_model );
1502 6         606 return;
1503             }
1504              
1505             sub model {
1506 3604     3604 1 6464 my $self = shift;
1507 3604   50     7615 my $config_class_name = shift
1508             || die "Model::get_model: missing config class name argument";
1509              
1510 3604 100       10065 $self->load($config_class_name)
1511             unless $self->normalized_model_exists($config_class_name);
1512              
1513 3603 100       39766 if ( not $self->model_defined($config_class_name) ) {
1514 259         3912 $loader_logger->debug("creating model $config_class_name");
1515              
1516 259         3209 my $model = $self->merge_included_class($config_class_name);
1517 258         1155 $self->_store_model( $config_class_name, $model );
1518             }
1519              
1520 3602   33     46000 return $self->_get_model($config_class_name)
1521             || croak "get_model error: unknown config class name: $config_class_name";
1522              
1523             }
1524              
1525             sub get_model {
1526 0     0 0 0 my ($self,$model) = @_;
1527 0         0 carp "get_model is deprecated in favor of get_model_clone";
1528 0         0 return $self->get_model_clone($model);
1529             }
1530              
1531             sub get_model_clone {
1532 960     960 1 4093 my ($self,$model) = @_;
1533 960         2740 return dclone($self->model($model));
1534             }
1535              
1536             # internal
1537             sub get_model_doc {
1538 3     3 0 21 my ( $self, $top_class_name, $done ) = @_;
1539              
1540 3   100     14 $done //= {};
1541 3 100       14 if ( not defined $self->normalized_model($top_class_name) ) {
1542 2         39 eval { $self->model($top_class_name); };
  2         15  
1543 2 100       85 if ($@) {
1544 1         2 my $e = $@;
1545 1 50       8 if ($e->isa('Config::Model::Exception::ModelDeclaration')) {
1546 1         6 Config::Model::Exception::Fatal->throw(
1547             message => "Unknown configuration class : $top_class_name ($@)"
1548             );
1549             }
1550             else {
1551 0         0 $e->rethrow;
1552             }
1553             }
1554             }
1555              
1556 2         21 my @classes = ($top_class_name);
1557 2         4 my %result;
1558              
1559 2         8 while (@classes) {
1560 18         30 my $class_name = shift @classes;
1561 18 100       43 next if $done->{$class_name} ;
1562              
1563 10   33     26 my $c_model = $self->model($class_name)
1564             || croak "get_model_doc model error : unknown config class name: $class_name";
1565              
1566 10         151 my $full_name = "Config::Model::models::$class_name";
1567              
1568 10         13 my %see_also;
1569              
1570 10         51 my @pod = (
1571              
1572             # Pod::Weaver compatibility
1573             "# PODNAME: $full_name",
1574             "# ABSTRACT: Configuration class " . $class_name, '',
1575              
1576             # assume utf8 for all docs
1577             "=encoding utf8", '',
1578              
1579             # plain old pod compatibility
1580             "=head1 NAME", '',
1581             "$full_name - Configuration class " . $class_name, '',
1582              
1583             "=head1 DESCRIPTION", '',
1584             "Configuration classes used by L<Config::Model>", ''
1585             );
1586              
1587 10         16 my %legalese;
1588              
1589 10         14 my $i = 0;
1590              
1591 10         20 my $class_desc = $c_model->{class_description};
1592 10 100       25 push @pod, $class_desc, '' if defined $class_desc;
1593              
1594 10         21 my @elt = ( "=head1 Elements", '' );
1595 10         13 foreach my $elt_name ( @{ $c_model->{element_list} } ) {
  10         23  
1596 75         120 my $elt_info = $c_model->{element}{$elt_name};
1597 75   50     194 my $summary = $elt_info->{summary} || '';
1598 75   33     124 $summary &&= " - $summary";
1599 75         162 push @elt, "=head2 $elt_name$summary", '';
1600 75         126 push @elt, $self->get_element_description($elt_info), '';
1601              
1602 75         141 foreach ( $elt_info, $elt_info->{cargo} ) {
1603 150 100       266 if ( my $ccn = $_->{config_class_name} ) {
1604 16         28 push @classes, $ccn;
1605 16         29 $see_also{$ccn} = 1;
1606             }
1607 150 50       247 if ( my $migr = $_->{migrate_from} ) {
1608 0         0 push @elt, $self->get_migrate_doc( $elt_name, 'is migrated with', $migr );
1609             }
1610 150 50       242 if ( my $migr = $_->{migrate_values_from} ) {
1611 0         0 push @elt, "Note: $elt_name values are migrated from '$migr'", '';
1612             }
1613 150 50       333 if ( my $comp = $_->{compute} ) {
1614 0         0 push @elt, $self->get_migrate_doc( $elt_name, 'is computed with', $comp );
1615             }
1616             }
1617             }
1618              
1619 10         23 foreach my $what (qw/author copyright license/) {
1620 30         45 my $item = $c_model->{$what};
1621 30 100       56 push @{ $legalese{$what} }, $item if $item;
  6         18  
1622             }
1623              
1624 10         15 my @end;
1625 10         18 foreach my $what (qw/author copyright license/) {
1626 30 100       37 next unless @{ $legalese{$what} || [] };
  30 100       104  
1627             push @end, "=head1 " . uc($what), '', '=over', '',
1628 6 100       17 ( map { ( "=item $_", '' ); } map { ref $_ ? @$_ : $_ } @{ $legalese{$what} } ),
  6         35  
  6         33  
  6         14  
1629             '', '=back', '';
1630             }
1631              
1632             my @see_also = (
1633             "=head1 SEE ALSO",
1634             '',
1635             "=over",
1636             '',
1637             "=item *",
1638             '',
1639             "L<cme>",
1640             '',
1641 10         43 ( map { ( "=item *", '', "L<Config::Model::models::$_>", '' ); } sort keys %see_also ),
  10         45  
1642             "=back",
1643             ''
1644             );
1645              
1646 10         109 $result{$full_name} = join( "\n", @pod, @elt, @see_also, @end, '=cut', '' ) . "\n";
1647 10         70 $done->{$class_name} = 1;
1648             }
1649 2         11 return \%result;
1650             }
1651              
1652             #
1653             # New subroutine "get_migrate_doc" extracted - Tue Jun 5 13:31:20 2012.
1654             #
1655             sub get_migrate_doc {
1656 0     0 0 0 my ( $self, $elt_name, $desc, $migr ) = @_;
1657              
1658 0         0 my $mv = $migr->{variables};
1659 0         0 my $mform = $migr->{formula};
1660              
1661 0 0       0 if ( $mform =~ /\n/) { $mform =~ s/^/ /mg; $mform = "\n\n$mform\n\n"; }
  0         0  
  0         0  
1662 0         0 else { $mform = "'C<$mform>' " }
1663              
1664             my $mdoc = "Note: $elt_name $desc ${mform}and with: \n\n=over\n\n=item *\n\n"
1665 0         0 . join( "\n\n=item *\n\n", map { qq!C<\$$_> => C<$mv->{$_}>! } sort keys %$mv );
  0         0  
1666 0 0       0 if ( my $rep = $migr->{replace} ) {
1667             $mdoc .= "\n\n=item *\n\n"
1668 0         0 . join( "\n\n=item *\n\n", map { qq!C<\$replace{$_}> => C<$rep->{$_}>! } sort keys %$rep );
  0         0  
1669             }
1670 0         0 $mdoc .= "\n\n=back\n\n";
1671              
1672 0         0 return ( $mdoc, '' );
1673             }
1674              
1675             sub get_element_description {
1676 75     75 0 121 my ( $self, $elt_info ) = @_;
1677              
1678 75         113 my $type = $elt_info->{type};
1679 75         95 my $cargo = $elt_info->{cargo};
1680 75         99 my $vt = $elt_info->{value_type};
1681              
1682 75         95 my $of = '';
1683 75         103 my $cargo_type = $cargo->{type};
1684 75         98 my $cargo_vt = $cargo->{value_type};
1685 75 100 66     148 $of = " of " . ( $cargo_vt or $cargo_type ) if defined $cargo_type;
1686              
1687 75   100     179 my $ccn = $elt_info->{config_class_name} || $cargo->{config_class_name};
1688 75 100       130 $of .= " of class L<$ccn|Config::Model::models::$ccn> " if $ccn;
1689              
1690 75   100     172 my $desc = $elt_info->{description} || '';
1691 75 100       112 if ($desc) {
1692 6 50       39 $desc .= '.' if $desc =~ /\w$/;
1693 6 50       22 $desc .= ' ' unless $desc =~ /\s$/;
1694             }
1695              
1696 75 50       148 if ( my $status = $elt_info->{status} ) {
1697 0         0 $desc .= 'B<' . ucfirst($status) . '> ';
1698             }
1699              
1700 75 100       128 my $info = $elt_info->{mandatory} ? 'Mandatory. ' : 'Optional. ';
1701              
1702 75   66     181 $info .= "Type " . ( $vt || $type ) . $of . '. ';
1703              
1704 75         107 foreach my $name (qw/choice/) {
1705 75         114 my $item = $elt_info->{$name};
1706 75 100       139 next unless defined $item;
1707 12         41 $info .= "$name: '" . join( "', '", @$item ) . "'. ";
1708             }
1709              
1710 75         103 my @default_info = ();
1711             # assemble in over item for string value_type
1712 75         96 foreach my $name (qw/default upstream_default/) {
1713 150         214 my $item = $elt_info->{$name};
1714 150 100       250 next unless defined $item;
1715 11         29 push @default_info, [$name, $item] ;
1716             }
1717              
1718 75         133 my $elt_help = $self->get_element_value_help($elt_info);
1719              
1720             # breaks pod if $info is multiline
1721 75         157 my $ret = $desc . "I< $info > ";
1722              
1723 75 100       134 if (@default_info) {
1724 11         20 $ret .= "\n\n=over 4\n\n";
1725 11         20 for ( @default_info) { $ret .= "=item $_->[0] value :\n\n$_->[1]\n\n"; }
  11         29  
1726 11         18 $ret .= "=back\n\n";
1727             }
1728              
1729 75         136 $ret.= $elt_help;
1730 75         174 return $ret;
1731             }
1732              
1733             sub get_element_value_help {
1734 75     75 0 115 my ( $self, $elt_info ) = @_;
1735              
1736 75         99 my $help = $elt_info->{help};
1737 75 100       172 return '' unless defined $help;
1738              
1739 2         7 my $help_text = "\n\nHere are some explanations on the possible values:\n\n=over\n\n";
1740 2         12 foreach my $v ( sort keys %$help ) {
1741 6         20 $help_text .= "=item '$v'\n\n$help->{$v}\n\n";
1742             }
1743              
1744 2         7 return $help_text . "=back\n\n";
1745             }
1746              
1747             sub generate_doc {
1748 2     2 1 23826 my ( $self, $top_class_name, $dir_str, $done ) = @_;
1749              
1750 2   50     19 $done //= {} ;
1751 2         8 my $res = $self->get_model_doc($top_class_name, $done);
1752              
1753 1 50 33     10 if ( defined $dir_str and $dir_str ) {
1754 1         8 foreach my $class_name ( sort keys %$res ) {
1755 5         717 my $dir = path($dir_str);
1756 5 50       105 $dir->mkpath() unless $dir->exists;
1757 5         110 my $file_path = $class_name;
1758 5         28 $file_path =~ s!::!/!g;
1759 5         22 my $pl_file = $dir->child("$file_path.pl");
1760 5 100       234 $pl_file->parent->mkpath unless $pl_file->parent->exists;
1761 5         890 my $pod_file = $dir->child("$file_path.pod");
1762              
1763 5         201 my $old = '';
1764 5 50       11 if ($pod_file->exists ) {
1765 0         0 $old = $pod_file->slurp_utf8;
1766             }
1767 5 50       127 if ( $old ne $res->{$class_name} ) {
1768 5         22 $pod_file->spew_utf8( $res->{$class_name} );
1769 5         7137 say "Wrote documentation in $pod_file";
1770             }
1771             }
1772             }
1773             else {
1774 0         0 foreach my $class_name ( sort keys %$res ) {
1775 0         0 print "########## $class_name ############ \n\n";
1776 0         0 print $res->{$class_name};
1777             }
1778             }
1779 1         137 return;
1780             }
1781              
1782             sub get_element_model {
1783 3     3 1 125 my $self = shift;
1784 3   50     10 my $config_class_name = shift
1785             || die "Model::get_element_model: missing config class name argument";
1786 3   50     12 my $element_name = shift
1787             || die "Model::get_element_model: missing element name argument";
1788              
1789 3         10 my $model = $self->model($config_class_name);
1790              
1791 3   33     59 my $element_m = $model->{element}{$element_name}
1792             || croak "get_element_model error: unknown element name: $element_name";
1793              
1794 3         186 return dclone($element_m);
1795             }
1796              
1797             # returns a hash ref containing the raw model, i.e. before expansion of
1798             # multiple keys (i.e. [qw/a b c/] => ... )
1799             # internal. For now ...
1800             sub get_normalized_model {
1801 0     0 0 0 my $self = shift;
1802 0         0 my $config_class_name = shift;
1803              
1804 0 0       0 $self->load($config_class_name)
1805             unless defined $self->normalized_model($config_class_name);
1806              
1807 0   0     0 my $normalized_model = $self->normalized_model($config_class_name)
1808             || croak "get_normalized_model error: unknown config class name: $config_class_name";
1809              
1810 0         0 return dclone($normalized_model);
1811             }
1812              
1813 104     104 1 145 sub get_element_name ($self, %args) {
  104         157  
  104         179  
  104         146  
1814             my $class = $args{class}
1815 104   33     234 || croak "get_element_name: missing 'class' parameter";
1816              
1817 104 50       210 if (delete $args{for}) {
1818 0         0 carp "get_element_name: 'for' parameter is deprecated";
1819             }
1820              
1821 104         219 my $model = $self->model($class);
1822 104         1314 my @result;
1823              
1824             # this is a bit convoluted, but the order of the returned element
1825             # must respect the order of the elements declared in the model by
1826             # the user
1827 104         146 foreach my $elt ( @{ $model->{element_list} } ) {
  104         198  
1828 542         847 my $elt_data = $model->{element}{$elt};
1829 542   66     1274 my $l = $elt_data->{level} || get_default_property('level');
1830 542 100       1333 push @result, $elt if $l ne 'hidden' ;
1831             }
1832              
1833 104 50       518 return wantarray ? @result : join( ' ', @result );
1834             }
1835              
1836 896     896 1 1838 sub get_element_property ($self, %args) {
  896         1370  
  896         2104  
  896         1200  
1837             my $elt = $args{element}
1838 896   33     2194 || croak "get_element_property: missing 'element' parameter";
1839             my $prop = $args{property}
1840 896   33     2128 || croak "get_element_property: missing 'property' parameter";
1841             my $class = $args{class}
1842 896   33     2178 || croak "get_element_property:: missing 'class' parameter";
1843              
1844 896         2178 my $model = $self->model($class);
1845              
1846             # must take into account 'accept' model parameter
1847 896 100       13612 if ( not defined $model->{element}{$elt} ) {
1848 52         314 $logger->debug("test accept for class $class elt $elt prop $prop");
1849 52         590 foreach my $acc_re ( @{ $model->{accept_list} } ) {
  52         135  
1850 110 100 66     1489 return $model->{accept}{$acc_re}{$prop} || get_default_property($prop)
1851             if $elt =~ /^$acc_re$/;
1852             }
1853             }
1854              
1855 844   66     1662 return $self->model($class)->{element}{$elt}{$prop}
1856             || get_default_property($prop);
1857             }
1858              
1859             sub list_class_element {
1860 1     1 1 1555 my $self = shift;
1861 1   50     8 my $pad = shift || '';
1862              
1863 1         4 my $res = '';
1864 1         9 foreach my $class_name ( $self->normalized_model_names ) {
1865 7         34 $res .= $self->list_one_class_element($class_name);
1866             }
1867 1         8 return $res;
1868             }
1869              
1870             sub list_one_class_element {
1871 7     7 0 11 my $self = shift;
1872 7         11 my $class_name = shift;
1873 7   50     20 my $pad = shift || '';
1874              
1875 7         18 my $res = $pad . "Class: $class_name\n";
1876 7         19 my $c_model = $self->normalized_model($class_name);
1877 7         104 my $elts = $c_model->{element_list}; # array ref
1878              
1879 7 100 66     42 return $res unless defined $elts and @$elts;
1880              
1881 6         16 foreach my $elt_name (@$elts) {
1882 36         107 my $type = $c_model->{element}{$elt_name}{type};
1883 36         76 $res .= $pad . " - $elt_name ($type)\n";
1884             }
1885 6         32 return $res;
1886             }
1887              
1888             __PACKAGE__->meta->make_immutable;
1889              
1890             1;
1891              
1892             # ABSTRACT: a framework to validate, migrate and edit configuration files
1893              
1894             __END__
1895              
1896             =pod
1897              
1898             =encoding UTF-8
1899              
1900             =head1 NAME
1901              
1902             Config::Model - a framework to validate, migrate and edit configuration files
1903              
1904             =head1 VERSION
1905              
1906             version 2.153
1907              
1908             =head1 SYNOPSIS
1909              
1910             =head2 Perl program to use an existing model
1911              
1912             use Config::Model qw(cme);
1913             # load, modify and save popcon configuration file
1914             cme('popcon')->modify("PARTICIPATE=yes");
1915              
1916             =head2 Command line to use an existing model
1917              
1918             # with App::Cme
1919             cme modify popcon 'PARTICIPATE=yes'
1920              
1921             =head2 Perl program with a custom model
1922              
1923             use Config::Model;
1924              
1925             # create new Model object
1926             my $model = Config::Model->new() ; # Config::Model object
1927              
1928             # create config model. A more complex model should be stored in a
1929             # file in lib/Config/Model/models. Then, run cme as explained below
1930             $model ->create_config_class (
1931             name => "MiniModel",
1932             element => [ [qw/foo bar baz/ ] => { type => 'leaf', value_type => 'uniline' }, ],
1933             rw_config => { backend => 'IniFile', auto_create => 1,
1934             config_dir => '.', file => 'mini.ini',
1935             }
1936             ) ;
1937              
1938             # create instance (Config::Model::Instance object)
1939             my $instance = $model->instance (root_class_name => 'MiniModel');
1940              
1941             # get configuration tree root
1942             my $cfg_root = $instance -> config_root ; # C::M:Node object
1943              
1944             # load some dummy data
1945             $cfg_root -> load("bar=BARV foo=FOOV baz=BAZV") ;
1946              
1947             # write new ini file
1948             $instance -> write_back;
1949              
1950             # now look for new mini.ini file un current directory
1951              
1952             =head2 Create a new model file and use it
1953              
1954             $ mkdir -p lib/Config/Model/models/
1955             $ echo "[ { name => 'MiniModel', \
1956             element => [ [qw/foo bar baz/ ] => { type => 'leaf', value_type => 'uniline' }, ], \
1957             rw_config => { backend => 'IniFile', auto_create => 1, \
1958             config_dir => '.', file => 'mini.ini', \
1959             } \
1960             } \
1961             ] ; " > lib/Config/Model/models/MiniModel.pl
1962             # require App::Cme
1963             $ cme modify -try MiniModel -dev bar=BARV foo=FOOV baz=BAZV
1964             $ cat mini.ini
1965              
1966             Note that model creation is easier running C<cme meta edit> with
1967             L<App::Cme> and L<Config::Model::Itself>.
1968              
1969             =head1 DESCRIPTION
1970              
1971             Config::Model enables a project developer to provide an interactive
1972             configuration editor (graphical, curses based or plain terminal) to
1973             users.
1974              
1975             To provide these tools, Config::Model needs:
1976              
1977             =over
1978              
1979             =item *
1980              
1981             A description of the structure and constraints of the project's configuration
1982             (fear not, a GUI is available with L<App::Cme>)
1983              
1984             =item *
1985              
1986             A module to read and write configuration data (aka a backend class).
1987              
1988             =back
1989              
1990             With the elements above, Config::Model generates interactive
1991             configuration editors (with integrated help and data validation).
1992             These editors can be graphical (with L<Config::Model::TkUI>), curses
1993             based (with L<Config::Model::CursesUI>) or based on ReadLine.
1994              
1995             Smaller models targeted for configuration upgrades can also be created:
1996              
1997             =over
1998              
1999             =item *
2000              
2001             only upgrade and migration specifications are required
2002              
2003             =item *
2004              
2005             unknown parameters can be accepted
2006              
2007             =back
2008              
2009             A command line is provided to perform configuration upgrade with a
2010             single command.
2011              
2012             =head2 How does this work ?
2013              
2014             Using this project, a typical configuration editor/validator/upgrader
2015             is made of 3 parts :
2016              
2017             GUI <--------> |---------------|
2018             CursesUI <---> | |---------| |
2019             | | Model | |
2020             ShellUI <----> | |---------| |<-----read-backend------- |-------------|
2021             | |----write-backend-------> | config file |
2022             FuseUI <-----> | Config::Model | |-------------|
2023             |---------------|
2024              
2025             =over
2026              
2027             =item 1.
2028              
2029             A reader and writer that parse the configuration file and transform its data
2030             into a tree representation within Config::Model. The values contained in this
2031             configuration tree can be written back in the configuration file(s).
2032              
2033             =item 2.
2034              
2035             A validation engine which is in charge of validating the content and
2036             structure of configuration stored in the configuration tree. This
2037             validation engine follows the structure and constraint declared in
2038             a configuration model. This model is a kind of schema for the
2039             configuration tree.
2040              
2041             =item 3.
2042              
2043             A user interface to modify the content of the configuration tree. A
2044             modification is validated immediately by the validation engine.
2045              
2046             =back
2047              
2048             The important part is the configuration model used by the validation
2049             engine. This model can be created or modified with a graphical editor
2050             (Config::Model::Iself).
2051              
2052             =head1 Question you may ask yourself
2053              
2054             =head2 Don't we already have some configuration validation tools ?
2055              
2056             You're probably thinking of tools like webmin. Yes, these tools exist
2057             and work fine, but they have their set of drawbacks.
2058              
2059             Usually, the validation of configuration data is done with a script
2060             which performs semantic validation and often ends up being quite
2061             complex (e.g. 2500 lines for Debian's xserver-xorg.config script which
2062             handles C<xorg.conf> file).
2063              
2064             In most cases, the configuration model is expressed in instructions
2065             (whatever programming language is used) and interspersed with a lot of
2066             processing to handle the actual configuration data.
2067              
2068             =head2 What's the advantage of this project ?
2069              
2070             Config::Model projects provide a way to get a validation engine where
2071             the configuration model is completely separated from the actual
2072             processing instructions.
2073              
2074             A configuration model can be created and modified with the graphical
2075             interface provide by L<Config::Model::Itself>. The model is saved in a
2076             declarative form (currently, a Perl data structure). Such a model is
2077             easier to maintain than a lot of code.
2078              
2079             The model specifies:
2080              
2081             =over
2082              
2083             =item *
2084              
2085             The structure of the configuration data (which can be queried by
2086             generic user interfaces)
2087              
2088             =item *
2089              
2090             The properties of each element (boundaries check, integer or string,
2091             enum like type, default value ...)
2092              
2093             =item *
2094              
2095             The targeted audience (beginner, advanced, master)
2096              
2097             =item *
2098              
2099             The on-line help
2100              
2101             =back
2102              
2103             So, in the end:
2104              
2105             =over
2106              
2107             =item *
2108              
2109             Maintenance and evolution of the configuration content is easier
2110              
2111             =item *
2112              
2113             User sees a *common* interface for *all* programs using this
2114             project.
2115              
2116             =item *
2117              
2118             Upgrade of configuration data is easier and sanity check is
2119             performed during the upgrade.
2120              
2121             =item *
2122              
2123             Audit of configuration is possible to check what was modified by the
2124             user compared to default values
2125              
2126             =back
2127              
2128             =head2 What about the user interface ?
2129              
2130             L<Config::Model> interface can be:
2131              
2132             =over
2133              
2134             =item *
2135              
2136             a shell-like interface (plain or based on Term::ReadLine).
2137              
2138             =item *
2139              
2140             Graphical with L<Config::Model::TkUI> (Perl/Tk interface).
2141              
2142             =item *
2143              
2144             based on curses with L<Config::Model::CursesUI>. This interface can be
2145             handy if your X server is down.
2146              
2147             =item *
2148              
2149             Through a virtual file system where every configuration parameter is mapped to a file.
2150             (Linux only)
2151              
2152             =back
2153              
2154             All these interfaces are generated from the configuration model.
2155              
2156             And configuration model can be created or modified with a graphical
2157             user interface (with C<cme meta edit> once L<Config::Model::Itself> is
2158             installed)
2159              
2160             =head2 What about configuration data storage ?
2161              
2162             Since the syntax of configuration files vary wildly form one application
2163             to another, people who want to use this framework may have to
2164             provide a dedicated parser/writer.
2165              
2166             To help with this task, this project provides writer/parsers for common
2167             format: INI style file and perl file. With the additional
2168             Config::Model::Backend::Augeas, Augeas library can be used to read and
2169             write some configuration files. See http://augeas.net for more
2170             details.
2171              
2172             =head2 Is there an example of a configuration model ?
2173              
2174             The "example" directory contains a configuration model example for
2175             C</etc/fstab> file. This example includes a small program that use
2176             this model to show some ways to extract configuration information.
2177              
2178             =head1 Mailing lists
2179              
2180             For more question, please send a mail to:
2181              
2182             config-model-users at lists.sourceforge.net
2183              
2184             =head1 Suggested reads to start
2185              
2186             =head2 Beginners
2187              
2188             =over
2189              
2190             =item *
2191              
2192             L<Config::Model::Manual::ModelCreationIntroduction>
2193              
2194             =item *
2195              
2196             L<Config::Model::Cookbook::CreateModelFromDoc>
2197              
2198             =back
2199              
2200             =head2 Advanced
2201              
2202             =over
2203              
2204             =item *
2205              
2206             L<Config::Model::models::Itself::Class>: This doc and its siblings
2207             describes all parameters available to create a model. These are the
2208             parameters available in the GUI launched by C<cme meta edit> command.
2209              
2210             =item *
2211              
2212             L<Config::Model::Manual::ModelCreationAdvanced>
2213              
2214             =back
2215              
2216             =head2 Masters
2217              
2218             use the source, Luke
2219              
2220             =head1 STOP
2221              
2222             The documentation below is quite detailed and is more a reference doc regarding
2223             C<Config::Model> class.
2224              
2225             For an introduction to model creation, please check:
2226             L<Config::Model::Manual::ModelCreationIntroduction>
2227              
2228             =head1 Storage backend, configuration reader and writer
2229              
2230             See L<Config::Model::BackendMgr> for details
2231              
2232             =head1 Validation engine
2233              
2234             C<Config::Model> provides a way to get a validation engine from a set
2235             of rules. This set of rules is called the configuration model.
2236              
2237             =head1 User interface
2238              
2239             The user interface uses some parts of the API to set and get
2240             configuration values. More importantly, a generic user interface
2241             needs to analyze the configuration model to be able to generate at
2242             run-time relevant configuration screens.
2243              
2244             A command line interface is provided in this module. Curses and Tk
2245             interfaces are provided by L<Config::Model::CursesUI> and
2246             L<Config::Model::TkUI>.
2247              
2248             =head1 Constructor
2249              
2250             my $model = Config::Model -> new ;
2251              
2252             creates an object to host your model.
2253              
2254             =head2 Constructor parameters
2255              
2256             =over
2257              
2258             =item log_level
2259              
2260             Specify minimal log level. Default is C<WARN>. Can be C<INFO>,
2261             C<DEBUG> or C<TRACE> to get more logs. Can also be C<ERROR> to get
2262             less traces.
2263              
2264             This parameter is used to override the log level specified in log
2265             configuration file.
2266              
2267             =back
2268              
2269             =head1 Configuration Model
2270              
2271             To validate a configuration tree, we must create a configuration model
2272             that defines all the properties of the validation engine you want to
2273             create.
2274              
2275             The configuration model is expressed in a declarative form (i.e. a
2276             Perl data structure which should be easier to maintain than a lot of
2277             code)
2278              
2279             Each configuration class may contain a set of:
2280              
2281             =over
2282              
2283             =item *
2284              
2285             node elements that refer to another configuration class
2286              
2287             =item *
2288              
2289             value elements that contain actual configuration data
2290              
2291             =item *
2292              
2293             list or hash elements that also contain several node or value elements
2294              
2295             =back
2296              
2297             The structure of your configuration tree is shaped by the a set of
2298             configuration classes that are used in node elements,
2299              
2300             The structure of the configuration data must be based on a tree
2301             structure. This structure has several advantages:
2302              
2303             =over
2304              
2305             =item *
2306              
2307             Unique path to get to a node or a leaf.
2308              
2309             =item *
2310              
2311             Simpler exploration and query
2312              
2313             =item *
2314              
2315             Simple hierarchy. Deletion of configuration items is simpler to grasp:
2316             when you cut a branch, all the leaves attached to that branch go down.
2317              
2318             =back
2319              
2320             But using a tree has also some drawbacks:
2321              
2322             =over 4
2323              
2324             =item *
2325              
2326             A complex configuration cannot be mapped on a tree. Some more
2327             relation between nodes and leaves must be added.
2328              
2329             =item *
2330              
2331             A configuration may actually be structured as a graph instead as a tree (for
2332             instance, any configuration that maps a service to a
2333             resource). The graph relation must be decomposed in a tree with
2334             special I<reference> relations that complete the tree to form a graph.
2335             See L<Config::Model::Value/Value Reference>
2336              
2337             =back
2338              
2339             Note: a configuration tree is a tree of objects. The model is declared
2340             with classes. The classes themselves have relations that closely match
2341             the relation of the object of the configuration tree. But the class
2342             need not to be declared in a tree structure (always better to reuse
2343             classes). But they must be declared as a DAG (directed acyclic graph).
2344             See also
2345             L<Directed acyclic graph on Wikipedia|http://en.wikipedia.org/wiki/Directed_acyclic_graph">More on DAGs>
2346              
2347             Each configuration class declaration specifies:
2348              
2349             =over
2350              
2351             =item *
2352              
2353             The C<name> of the class (mandatory)
2354              
2355             =item *
2356              
2357             A C<class_description> used in user interfaces (optional)
2358              
2359             =item *
2360              
2361             Optional include specification to avoid duplicate declaration of elements.
2362              
2363             =item *
2364              
2365             The class elements
2366              
2367             =back
2368              
2369             Each element specifies:
2370              
2371             =over
2372              
2373             =item *
2374              
2375             Most importantly, the type of the element (mostly C<leaf>, or C<node>)
2376              
2377             =item *
2378              
2379             The properties of each element (boundaries, check, integer or string,
2380             enum like type ...)
2381              
2382             =item *
2383              
2384             The default values of parameters (if any)
2385              
2386             =item *
2387              
2388             Whether the parameter is mandatory
2389              
2390             =item *
2391              
2392             Targeted audience (beginner, advance, master), i.e. the level of
2393             expertise required to tinker a parameter (to hide expert parameters
2394             from newbie eyes)
2395              
2396             =item *
2397              
2398             On-line help (for each parameter or value of parameter)
2399              
2400             =back
2401              
2402             See L<Config::Model::Node> for details on how to declare a
2403             configuration class.
2404              
2405             Example:
2406              
2407             $ cat lib/Config/Model/models/Xorg.pl
2408             [
2409             {
2410             name => 'Xorg',
2411             class_description => 'Top level Xorg configuration.',
2412             include => [ 'Xorg::ConfigDir'],
2413             element => [
2414             Files => {
2415             type => 'node',
2416             description => 'File pathnames',
2417             config_class_name => 'Xorg::Files'
2418             },
2419             # snip
2420             ]
2421             },
2422             {
2423             name => 'Xorg::DRI',
2424             element => [
2425             Mode => {
2426             type => 'leaf',
2427             value_type => 'uniline',
2428             description => 'DRI mode, usually set to 0666'
2429             }
2430             ]
2431             }
2432             ];
2433              
2434             =head1 Configuration instance methods
2435              
2436             A configuration instance is created from a model and is the starting
2437             point of a configuration tree.
2438              
2439             =head2 instance
2440              
2441             An instance must be created with a model name (using the root class
2442             name) or an application name (as shown by "L<cme> C<list>" command).
2443              
2444             For example:
2445              
2446             my $model = Config::Model->new() ;
2447             $model->instance( application => 'approx');
2448              
2449             Or:
2450              
2451             my $model = Config::Model->new() ;
2452             # note that the model class is slightly different compared to
2453             # application name
2454             $model->instance( root_class_name => 'Approx');
2455              
2456             A custom configuration class can also be used with C<root_class_name> parameter:
2457              
2458             my $model = Config::Model->new() ;
2459             # create_config_class is described below
2460             $model ->create_config_class (
2461             name => "SomeRootClass",
2462             element => [ ... ]
2463             ) ;
2464              
2465             # instance name is 'default'
2466             my $inst = $model->instance (root_class_name => 'SomeRootClass');
2467              
2468             You can create several separated instances from a model using
2469             C<name> option:
2470              
2471             # instance name is 'default'
2472             my $inst = $model->instance (
2473             root_class_name => 'SomeRootClass',
2474             name => 'test1'
2475             );
2476              
2477             Usually, model files are loaded automatically using a path matching
2478             C<root_class_name> (e.g. configuration class C<Foo::Bar> is stored in
2479             C<Foo/Bar.pl>. You can choose to specify the file containing
2480             the model with C<model_file> parameter. This is mostly useful for
2481             tests.
2482              
2483             The C<instance> method can also retrieve an instance that has already
2484             been created:
2485              
2486             my $inst = $model->instance( name => 'test1' );
2487              
2488             =head2 get_instance
2489              
2490             Retrieve an existing instance using its name.
2491              
2492             my $inst = $model->get_instance('test1' );
2493              
2494             =head2 has_instance
2495              
2496             Check if an instance name already exists
2497              
2498             my $maybe = $model->has_instance('test1');
2499              
2500             =head2 cme
2501              
2502             This method is syntactic sugar for short program. It creates a new
2503             C<Config::Model> object and returns a new instance.
2504              
2505             C<cme> arguments are passed to L</instance> method, except
2506             C<force-load>.
2507              
2508             Like L<cme> command, C<cme> functions accepts C<force-load>
2509             parameters. When this argument is true, the instance is created with
2510             C<check => 'no'>. Hence bad values are stored in C<cme> and must be
2511             corrected before saving back the data.
2512              
2513             =head1 Configuration class
2514              
2515             A configuration class is made of series of elements which are detailed
2516             in L<Config::Model::Node>.
2517              
2518             Whatever its type (node, leaf,... ), each element of a node has
2519             several other properties:
2520              
2521             =over
2522              
2523             =item level
2524              
2525             Level is C<important>, C<normal> or C<hidden>.
2526              
2527             The level is used to set how configuration data is presented to the
2528             user in browsing mode. C<Important> elements are shown to the user no
2529             matter what. C<hidden> elements are well, hidden. Their purpose is
2530             explained with the I<warp> notion.
2531              
2532             =item status
2533              
2534             Status is C<obsolete>, C<deprecated> or C<standard> (default).
2535              
2536             Using a deprecated element raises a warning. Using an obsolete
2537             element raises an exception.
2538              
2539             =item description
2540              
2541             Description of the element. This description is used while
2542             generating user interfaces.
2543              
2544             =item summary
2545              
2546             Summary of the element. This description is used while generating
2547             a user interfaces and may be used in comments when writing the
2548             configuration file.
2549              
2550             =item class_description
2551              
2552             Description of the configuration class. This description is used
2553             while generating user interfaces.
2554              
2555             =item generated_by
2556              
2557             Mention with a descriptive string if this class was generated by a
2558             program. This parameter is currently reserved for
2559             L<Config::Model::Itself> model editor.
2560              
2561             =item include
2562              
2563             Include element description from another class.
2564              
2565             include => 'AnotherClass' ,
2566              
2567             or
2568              
2569             include => [qw/ClassOne ClassTwo/]
2570              
2571             In a configuration class, the order of the element is important. For
2572             instance if C<foo> is warped by C<bar>, you must declare C<bar>
2573             element before C<foo>.
2574              
2575             When including another class, you may wish to insert the included
2576             elements after a specific element of your including class:
2577              
2578             # say AnotherClass contains element xyz
2579             include => 'AnotherClass' ,
2580             include_after => "foo" ,
2581             element => [ bar => ... , foo => ... , baz => ... ]
2582              
2583             Now the element of your class are:
2584              
2585             ( bar , foo , xyz , baz )
2586              
2587             Note that include may not clobber an existing element.
2588              
2589             =item include_backend
2590              
2591             Include read/write specification from another class.
2592              
2593             include_backend => 'AnotherClass' ,
2594              
2595             or
2596              
2597             include_backend => [qw/ClassOne ClassTwo/]
2598              
2599             =back
2600              
2601             Note that include may not clobber an existing read/write specification.
2602              
2603             =head2 create_config_class
2604              
2605             This method creates configuration classes. The parameters are
2606             described above and are forwarded to L<Config::Model::Node>
2607             constructor. See
2608             L<Config::Model::Node/"Configuration class declaration">
2609             for more details on configuration class parameters.
2610              
2611             Example:
2612              
2613             my $model = Config::Model -> new ;
2614              
2615             $model->create_config_class
2616             (
2617             config_class_name => 'SomeRootClass',
2618             description => [ X => 'X-ray' ],
2619             level => [ 'tree_macro' => 'important' ] ,
2620             class_description => "SomeRootClass description",
2621             element => [ ... ]
2622             ) ;
2623              
2624             For convenience, C<level> and C<description> parameters
2625             can also be declared within the element declaration:
2626              
2627             $model->create_config_class
2628             (
2629             config_class_name => 'SomeRootClass',
2630             class_description => "SomeRootClass description",
2631             'element'
2632             => [
2633             tree_macro => { level => 'important'},
2634             X => { description => 'X-ray', } ,
2635             ]
2636             ) ;
2637              
2638             =head1 Load predeclared model
2639              
2640             You can also load predeclared model.
2641              
2642             =head2 load( <model_name> )
2643              
2644             This method opens the model directory and execute a C<.pl>
2645             file containing the model declaration,
2646              
2647             This perl file must return an array ref to declare models. E.g.:
2648              
2649             [
2650             [
2651             name => 'Class_1',
2652             element => [ ... ]
2653             ],
2654             [
2655             name => 'Class_2',
2656             element => [ ... ]
2657             ]
2658             ];
2659              
2660             do not put C<1;> at the end or C<load> will not work
2661              
2662             When a model name contain a C<::> (e.g C<Foo::Bar>), C<load> looks for
2663             a file named C<Foo/Bar.pl>.
2664              
2665             This method also searches in C<Foo/Bar.d> directory for additional model information.
2666             Model snippet found there are loaded with L<augment_config_class>.
2667              
2668             Returns a list containing the names of the loaded classes. For instance, if
2669             C<Foo/Bar.pl> contains a model for C<Foo::Bar> and C<Foo::Bar2>, C<load>
2670             returns C<( 'Foo::Bar' , 'Foo::Bar2' )>.
2671              
2672             =head2 augment_config_class (name => '...', class_data )
2673              
2674             Enhance the feature of a configuration class. This method uses the same parameters
2675             as L<create_config_class>. See
2676             L<Config::Model::Manual::ModelCreationAdvanced/"Model Plugin">
2677             for more details on creating model plugins.
2678              
2679             =head1 Model query
2680              
2681             =head2 model
2682              
2683             Returns a hash containing the model declaration of the passed model
2684             name. Do not modify the content of the returned data structure.
2685              
2686             my $cloned = $model->model('Foo');
2687              
2688             =head2 get_model_clone
2689              
2690             Like C<model>, returns a hash containing the model declaration of the passed model
2691             name, this time in a deep clone of the data structure.
2692              
2693             my $cloned = $model->get_model_clone('Foo');
2694              
2695             =head2 generate_doc ( top_class_name , directory , [ \%done ] )
2696              
2697             Generate POD document for configuration class top_class_name and all
2698             classes used by top_class_name, and write them in specified directory.
2699              
2700             C<\%done> is an optional reference to a hash used to avoid writing
2701             twice the same documentation when this method is called several times.
2702              
2703             =head2 get_element_model( config_class_name , element)
2704              
2705             Return a hash containing the model declaration for the specified class
2706             and element.
2707              
2708             =head2 get_element_name( class => Foo )
2709              
2710             Get all names of the elements of class C<Foo>.
2711              
2712             =head2 get_element_property
2713              
2714             Returns the property of an element from the model.
2715              
2716             Parameters are:
2717              
2718             =over
2719              
2720             =item class
2721              
2722             =item element
2723              
2724             =item property
2725              
2726             =back
2727              
2728             =head2 list_class_element
2729              
2730             Returns a string listing all the class and elements. Useful for
2731             debugging your configuration model.
2732              
2733             =head1 Error handling
2734              
2735             Errors are handled with an exception mechanism.
2736              
2737             When a strongly typed Value object gets an authorized value, it raises
2738             an exception. If this exception is not caught, the programs exits.
2739              
2740             See L<Config::Model::Exception|Config::Model::Exception> for details on
2741             the various exception classes provided with C<Config::Model>.
2742              
2743             =head1 Logging
2744              
2745             See L<cme/Logging>
2746              
2747             =head2 initialize_log4perl
2748              
2749             This method can be called to load L<Log::Log4perl> configuration from
2750             C<~/.log4config-model>, or from C</etc/log4config-model.conf> files or from
2751             L<default configuration|https://github.com/dod38fr/config-model/blob/master/lib/Config/Model/log4perl.conf>.
2752              
2753             Accepts C<verbose> parameter with a list of log classes that are added
2754             to the log4perl configuration read above.
2755              
2756             For instance, with C<< verbose => 'Loader' >>, log4perl is initialised with
2757              
2758             log4perl.logger.Verbose.Loader = INFO, PlainMsgOnScreen
2759              
2760             Likewise, with C<< verbose => [ 'Loader', 'Foo' ] >>,
2761             log4perl is initialised with:
2762              
2763             log4perl.logger.Verbose.Loader = INFO, PlainMsgOnScreen
2764             log4perl.logger.Verbose.Foo = INFO, PlainMsgOnScreen
2765              
2766             Currently, this module supports only C<Loader> as verbose parameters.
2767              
2768             =head1 BUGS
2769              
2770             Given Murphy's law, the author is fairly confident that you will find
2771             bugs or miss some features. Please report them to
2772             https://github.com/dod38fr/config-model/issues
2773             The author will be notified, and then you'll automatically be
2774             notified of progress on your bug.
2775              
2776             =head1 FEEDBACK
2777              
2778             Feedback from users are highly desired. If you find this module useful, please
2779             share your use cases, success stories with the author or with the config-model-
2780             users mailing list.
2781              
2782             =head1 PROJECT FOUNDER
2783              
2784             Dominique Dumont, "ddumont@cpan.org"
2785              
2786             =head1 CREDITS
2787              
2788             Contributors to this project are listed in alphabetical order:
2789              
2790             Harley Pig
2791              
2792             Ilya Arosov
2793              
2794             Jose Luis Perez Diez
2795              
2796             Krzysztof Tyszecki
2797              
2798             Mathieu Arnold
2799              
2800             Mohammad S Anwar
2801              
2802             Topi Miettinen
2803              
2804             Many thanks for your help
2805              
2806             =head1 SEE ALSO
2807              
2808             L<Config::Model::Instance>,
2809              
2810             L<https://github.com/dod38fr/config-model/wiki>
2811              
2812             L<https://github.com/dod38fr/config-model/wiki/Creating-models>
2813              
2814             =head2 Model elements
2815              
2816             The arrow shows inheritance between classes
2817              
2818             =over
2819              
2820             =item *
2821              
2822             L<Config::Model::Node> <- L<Config::Model::AnyThing>
2823              
2824             =item *
2825              
2826             L<Config::Model::HashId> <- L<Config::Model::AnyId> <- L<Config::Model::AnyThing>
2827              
2828             =item *
2829              
2830             L<Config::Model::ListId> <- L<Config::Model::AnyId> <- L<Config::Model::AnyThing>
2831              
2832             =item *
2833              
2834             L<Config::Model::Value> <- L<Config::Model::AnyThing>
2835              
2836             =item *
2837              
2838             L<Config::Model::CheckList> <- L<Config::Model::AnyThing>
2839              
2840             =item *
2841              
2842             L<Config::Model::WarpedNode> <- L<Config::Model::AnyThing>
2843              
2844             =back
2845              
2846             =head2 command line
2847              
2848             L<cme>.
2849              
2850             =head2 Read and write backends
2851              
2852             =over
2853              
2854             =item *
2855              
2856             L<Config::Model::Backend::Fstab> <- L<Config::Model::Backend::Any>
2857              
2858             =item *
2859              
2860             L<Config::Model::Backend::IniFile> <- L<Config::Model::Backend::Any>
2861              
2862             =item *
2863              
2864             L<Config::Model::Backend::PlainFile> <- L<Config::Model::Backend::Any>
2865              
2866             =item *
2867              
2868             L<Config::Model::Backend::ShellVar> <- L<Config::Model::Backend::Any>
2869              
2870             =back
2871              
2872             =head2 Model utilities
2873              
2874             =over
2875              
2876             =item *
2877              
2878             L<Config::Model::Annotation>
2879              
2880             =item *
2881              
2882             L<Config::Model::BackendMgr>: Used by C<Config::Model::Node> object
2883              
2884             =item *
2885              
2886             L<Config::Model::Describe>
2887              
2888             =item *
2889              
2890             L<Config::Model::Dumper>
2891              
2892             =item *
2893              
2894             L<Config::Model::DumpAsData>
2895              
2896             =item *
2897              
2898             L<Config::Model::IdElementReference>
2899              
2900             =item *
2901              
2902             L<Config::Model::Iterator>
2903              
2904             =item *
2905              
2906             L<Config::Model::Loader>
2907              
2908             =item *
2909              
2910             L<Config::Model::ObjTreeScanner>
2911              
2912             =item *
2913              
2914             L<Config::Model::Report>
2915              
2916             =item *
2917              
2918             L<Config::Model::Searcher>: Search element in configuration model.
2919              
2920             =item *
2921              
2922             L<Config::Model::SimpleUI>
2923              
2924             =item *
2925              
2926             L<Config::Model::TreeSearcher>: Search string or regexp in configuration tree.
2927              
2928             =item *
2929              
2930             L<Config::Model::TermUI>
2931              
2932             =item *
2933              
2934             L<Config::Model::Iterator>
2935              
2936             =item *
2937              
2938             L<Config::Model::ValueComputer>
2939              
2940             =item *
2941              
2942             L<Config::Model::Warper>
2943              
2944             =back
2945              
2946             =head2 Test framework
2947              
2948             =over
2949              
2950             =item *
2951              
2952             L<Config::Model::Tester>
2953              
2954             =back
2955              
2956             =head1 AUTHOR
2957              
2958             Dominique Dumont
2959              
2960             =head1 COPYRIGHT AND LICENSE
2961              
2962             This software is Copyright (c) 2005-2022 by Dominique Dumont.
2963              
2964             This is free software, licensed under:
2965              
2966             The GNU Lesser General Public License, Version 2.1, February 1999
2967              
2968             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
2969              
2970             =head1 SUPPORT
2971              
2972             =head2 Websites
2973              
2974             The following websites have more information about this module, and may be of help to you. As always,
2975             in addition to those websites please use your favorite search engine to discover more resources.
2976              
2977             =over 4
2978              
2979             =item *
2980              
2981             CPANTS
2982              
2983             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
2984              
2985             L<http://cpants.cpanauthors.org/dist/Config-Model>
2986              
2987             =item *
2988              
2989             CPAN Testers
2990              
2991             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
2992              
2993             L<http://www.cpantesters.org/distro/C/Config-Model>
2994              
2995             =item *
2996              
2997             CPAN Testers Matrix
2998              
2999             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
3000              
3001             L<http://matrix.cpantesters.org/?dist=Config-Model>
3002              
3003             =item *
3004              
3005             CPAN Testers Dependencies
3006              
3007             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
3008              
3009             L<http://deps.cpantesters.org/?module=Config::Model>
3010              
3011             =back
3012              
3013             =head2 Bugs / Feature Requests
3014              
3015             Please report any bugs or feature requests by email to C<ddumont at cpan.org>, or through
3016             the web interface at L<https://github.com/dod38fr/config-model/issues>. You will be automatically notified of any
3017             progress on the request by the system.
3018              
3019             =head2 Source Code
3020              
3021             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
3022             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
3023             from your repository :)
3024              
3025             L<http://github.com/dod38fr/config-model>
3026              
3027             git clone git://github.com/dod38fr/config-model.git
3028              
3029             =cut