File Coverage

blib/lib/Config/Model/Itself.pm
Criterion Covered Total %
statement 376 405 92.8
branch 61 112 54.4
condition 59 105 56.1
subroutine 36 40 90.0
pod 6 18 33.3
total 538 680 79.1


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model-Itself
3             #
4             # This software is Copyright (c) 2007-2017 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::Itself ;
11             $Config::Model::Itself::VERSION = '2.012';
12 8     8   2952332 use Mouse ;
  8         65  
  8         60  
13 8     8   3262 use Config::Model 2.111;
  8         184  
  8         297  
14 8     8   111 use 5.010;
  8         23  
15              
16 8     8   40 use IO::File ;
  8         11  
  8         1106  
17 8     8   66 use Log::Log4perl 1.11;
  8         113  
  8         49  
18 8     8   371 use Carp ;
  8         14  
  8         445  
19 8     8   53 use Data::Dumper ;
  8         17  
  8         351  
20 8     8   60 use File::Find ;
  8         22  
  8         394  
21 8     8   53 use File::Path ;
  8         21  
  8         334  
22 8     8   53 use File::Basename ;
  8         16  
  8         395  
23 8     8   2268 use Data::Compare ;
  8         73951  
  8         57  
24 8     8   22975 use Path::Tiny 0.062;
  8         194  
  8         399  
25 8     8   52 use Mouse::Util::TypeConstraints;
  8         15  
  8         95  
26              
27             my $logger = Log::Log4perl::get_logger("Backend::Itself");
28              
29             subtype 'ModelPathTiny' => as 'Object' => where { $_->isa('Path::Tiny') };
30              
31             coerce 'ModelPathTiny' => from 'Str' => via {path($_)} ;
32              
33             # find all .pl file in model_dir and load them...
34              
35             around BUILDARGS => sub {
36             my $orig = shift;
37             my $class = shift;
38             my %args = @_;
39              
40             my $legacy = delete $args{model_object};
41             if ($legacy) {
42             $args{config_model} = $legacy->instance->config_model;
43             $args{meta_instance} = $legacy->instance;
44             $args{meta_root} = $legacy;
45             }
46             return $class->$orig( %args );
47             };
48              
49             has 'config_model' => (
50             is => 'ro',
51             isa => 'Config::Model',
52             lazy_build => 1,
53             ) ;
54              
55              
56             sub _build_config_model {
57 0     0   0 my $self = shift;
58             # don't trigger builders below
59 0 0       0 if ($self->{meta_root}) {
    0          
60 0         0 return $self->meta_root->instance->config_model;
61             }
62             elsif ($self->{meta_instance}) {
63 0         0 return $self->meta_instance->config_model;
64             }
65             else {
66 0         0 return Config::Model -> new ( ) ;
67             }
68             }
69              
70             has check => (is =>'ro', isa => 'Bool', default => 1) ;
71              
72             has 'meta_instance' => (
73             is =>'ro',
74             isa =>'Config::Model::Instance',
75             lazy_build => 1,
76             ) ;
77              
78             sub _build_meta_instance {
79 0     0   0 my $self = shift;
80              
81             # don't trigger builders below
82 0 0       0 if ($self->{meta_root}) {
83 0         0 return $self->meta_root->instance;
84             }
85             else {
86             # load Config::Model model
87 0         0 return $self->config_model->instance (
88             root_class_name => 'Itself::Model' ,
89             instance_name => 'meta_model' ,
90             check => $self->check,
91             );
92             }
93              
94             }
95              
96             has meta_root => (
97             is =>'ro',
98             isa =>'Config::Model::Node',
99             lazy_build => 1,
100             ) ;
101              
102             sub _build_meta_root {
103 0     0   0 my $self = shift;
104              
105 0         0 return $self->meta_instance -> config_root ;
106             }
107              
108             has cm_lib_dir => (
109             is =>'ro',
110             isa => 'ModelPathTiny',
111             lazy_build => 1,
112             coerce => 1
113             ) ;
114              
115             sub _build_cm_lib_dir {
116 0     0   0 my $self = shift;
117 0         0 my $p = path('lib/Config/Model');
118 0 0       0 if (! $p->is_dir) {
119 0 0       0 $p->mkpath(0, 0755) || die "can't create $p:$!";
120             }
121 0         0 return $p;
122             }
123              
124             has force_write => (is =>'ro', isa => 'Bool', default => 0) ;
125             has root_model => (is =>'ro', isa => 'str');
126              
127             has modified_classes => (
128             is =>'rw',
129             isa =>'HashRef[Bool]',
130             traits => ['Hash'],
131             default => sub { {} } ,
132             handles => {
133             clear_classes => 'clear',
134             set_class => 'set',
135             class_was_changed => 'get' ,
136             class_known => 'exists',
137             }
138             ) ;
139              
140             has model_dir => (
141             is => 'ro',
142             isa => 'ModelPathTiny',
143             lazy_build => 1,
144             );
145              
146             sub _build_model_dir {
147 9     9   28 my $self = shift;
148 9         76 my $md = $self->cm_lib_dir->child('models');
149 9         421 $md->mkpath;
150 9         1260 return $md;
151             }
152              
153             sub BUILD {
154 9     9 1 26 my $self = shift;
155              
156             my $cb = sub {
157 3832     3832   23588169 my %args = @_ ;
158 3832   50     15466 my $p = $args{path} || '' ;
159 3832 100       21413 return unless $p =~ /^class/ ;
160 3829 50       9950 return unless $args{index}; # may be empty when class order is changed
161 3829 100       16091 return if $self->class_was_changed($args{index}) ;
162 856         17067 $logger->info("class $args{index} was modified");
163              
164 856         9991 $self->add_modified_class($args{index}) ;
165 9         54 } ;
166 9         109 $self->meta_instance -> on_change_cb($cb) ;
167              
168             }
169              
170             sub add_tracked_class {
171 123     123 0 229 my $self = shift;
172 123         464 $self->set_class(shift,0) ;
173             }
174              
175             sub add_modified_class {
176 979     979 0 8916 my $self = shift;
177 979         3853 $self->set_class(shift,1) ;
178             }
179              
180             sub class_needs_write {
181 65     65 0 159 my $self = shift;
182 65         137 my $name = shift;
183 65   66     569 return ($self->force_write or not $self->class_known($name) or $self->class_was_changed($name)) ;
184             }
185              
186             sub read_app_files {
187 7     7 0 15 my $self = shift;
188 7   100     32 my $force_load = shift || 0;
189 7         14 my $read_from = shift ;
190 7         16 my $application = shift ;
191              
192 7   33     77 my $app_dir = $read_from || $self->model_dir->parent;
193 7         610 my %apps;
194 7         30 $logger->info("reading app files from ".$app_dir);
195 7         178 foreach my $dir ( $app_dir->children(qr/\.d$/) ) {
196              
197 3         419 $logger->info("reading app dir ".$dir);
198 3         42 foreach my $file ( $dir->children() ) {
199 3 50       295 next if $file =~ m!/README!;
200 3 50       29 next if $file =~ /(~|\.bak|\.orig)$/;
201 3 50 33     45 next if $application and $file->basename ne $application;
202              
203             # bad categories are filtered by the model
204 3         20 my %data = ( category => $dir->basename('.d') );
205 3         196 $logger->info("reading app file ".$file);
206              
207 3         52 foreach ($file->lines({ chomp => 1})) {
208 6         651 s/^\s+//;
209 6         21 s/\s+$//;
210 6         15 s/#.*//;
211 6         34 my ( $k, $v ) = split /\s*=\s*/;
212 6 50       20 next unless $v;
213 6         23 $data{$k} = $v;
214             }
215              
216 3         14 my $appli = $file->basename;
217 3         91 $apps{$appli} = $data{model} ;
218              
219 3 50       41 $self->meta_root->load_data(
220             data => { application => { $appli => \%data } },
221             check => $force_load ? 'no' : 'yes'
222             ) ;
223             }
224             }
225              
226 7         165100 return \%apps;
227             }
228              
229             sub read_all {
230 7     7 0 102 my $self = shift ;
231 7         31 my %args = @_ ;
232              
233 7   100     48 my $force_load = delete $args{force_load} || 0 ;
234 7         20 my $read_from ;
235             my $model_dir ;
236 7 50       27 if ($args{read_from}) {
237 0         0 $read_from = path (delete $args{read_from});
238 0 0       0 die "Cannot read from unknown dir ".$read_from unless $read_from->is_dir;
239 0         0 $model_dir = $read_from->child('models');
240 0 0       0 die "Cannot read from unknown dir ".$model_dir unless $model_dir->is_dir;
241             }
242              
243 7         33 my $apps = $self-> read_app_files($force_load, $read_from, delete $args{application});
244              
245 7   50     40 my $root_model_arg = delete $args{root_model} || '';
246 7   33     49 my $model = $apps->{$root_model_arg} || $root_model_arg ;
247 7         20 my $legacy = delete $args{legacy} ;
248              
249 7 50       23 croak "read_all: unexpected parameters ",join(' ', keys %args) if %args ;
250              
251 7         33 my $dir = $self->model_dir;
252 7         36 $dir->mkpath ;
253              
254 7         466 my $root_model_file = $model ;
255 7         26 $root_model_file =~ s!::!/!g ;
256 7   33     54 my $read_dir = $model_dir || $dir;
257 7         38 $logger->info("searching model files in ".$read_dir);
258              
259 7         100 my @files ;
260             my $wanted = sub {
261 103 50 100 103   10539 push @files, $_ if ( $_->is_file and /\.pl$/
      66        
      66        
262             and m!$read_dir/$root_model_file\b!
263             and not m!\.d/!
264             ) ;
265 7         43 } ;
266 7         59 $read_dir->visit($wanted, { recurse => 1} ) ;
267              
268 7         345 my $i = $self->meta_instance ;
269              
270 7         41 my %read_models ;
271             my %pod_data ;
272 7         0 my %class_file_map ;
273              
274 7         0 my @all_models;
275 7         23 for my $file (@files) {
276 63         376 $logger->info("loading config file $file");
277              
278             # now apply some translation to read model
279             # - translate legacy warp parameters
280             # - expand elements name
281 63 100       1191 my @legacy = $legacy ? ( legacy => $legacy ) : () ;
282 63         789 my $tmp_model = Config::Model -> new( skip_include => 1, @legacy ) ;
283              
284             # @models order is important to write configuration class back in the same
285             # order as the declaration
286 63         6099 my @models = $tmp_model -> load ( 'Tmp' , $file->absolute ) ;
287 63         290654 push @all_models, @models;
288              
289 63         209 my $rel_file = $file ;
290 63         357 $rel_file =~ s/^$read_dir\/?//;
291 63 50       1256 die "wrong reg_exp" if $file eq $rel_file ;
292 63         549 $class_file_map{$rel_file} = \@models ;
293              
294             # - move experience, description and level status into parameter info.
295 63         173 foreach my $model_name (@models) {
296             # no need to dclone model as Config::Model object is temporary
297 123         482 my $raw_model = $tmp_model -> get_raw_model( $model_name ) ;
298 123         2528 my $new_model = $tmp_model -> get_model( $model_name ) ;
299              
300 123         33189 $self->upgrade_model($model_name, $new_model);
301              
302             # track read class to identify later classes added by user
303 123         398 $self->add_tracked_class($model_name);
304              
305             # some modifications may be done to cope with older model styles. If a modif
306             # was done, mark the class as changed so it will be saved later
307 123 50       6173 $self->add_modified_class($model_name) unless Compare($raw_model, $new_model) ;
308              
309 123         5223 foreach my $item (qw/description summary level experience status/) {
310 615         860 foreach my $elt_name (keys %{$new_model->{element}}) {
  615         1643  
311 3795         6357 my $moved_data = delete $new_model->{$item}{$elt_name} ;
312 3795 50       6980 next unless defined $moved_data ;
313 0         0 $new_model->{element}{$elt_name}{$item} = $moved_data ;
314             }
315 615         1427 delete $new_model->{$item} ;
316             }
317              
318             # Since accept specs and elements are stored in a ordered hash,
319             # load_data expects a array ref instead of a hash ref.
320             # Build this array ref taking the order into
321             # account
322 123         227 foreach my $what (qw/element accept/) {
323 246         648 my $list = delete $new_model -> {$what.'_list'} ;
324 246         497 my $h = delete $new_model -> {$what} ;
325 246         542 $new_model -> {$what} = [] ;
326             map {
327 246         571 push @{$new_model->{$what}}, $_, $h->{$_}
  765         1060  
  765         2254  
328             } @$list ;
329             }
330              
331             # remove hash key with undefined values
332 123         380 map { delete $new_model->{$_} unless defined $new_model->{$_}
333 351 50 33     1842 and $new_model->{$_} ne ''
334             } keys %$new_model ;
335 123         4413 $read_models{$model_name} = $new_model ;
336             }
337              
338             }
339              
340 7   33     243 $self->{root_model} = $model || (sort @all_models)[0];
341              
342             # Create all classes listed in %read_models to avoid problems with
343             # include statement while calling load_data
344 7         49 my $root_obj = $self->meta_root ;
345 7         62 my $class_element = $root_obj->fetch_element('class') ;
346 7         136825 map { $class_element->fetch_with_id($_) } sort keys %read_models ;
  123         149827  
347              
348             #require Tk::ObjScanner; Tk::ObjScanner::scan_object(\%read_models) ;
349              
350 7         5829 $logger->info("loading all extracted data in Config::Model::Itself");
351             # load with a array ref to avoid warnings about missing order
352 7 100       193 $root_obj->load_data(
353             data => {class => [ %read_models ] },
354             check => $force_load ? 'no' : 'yes'
355             ) ;
356              
357             # load annotations and comment header
358 7         164077 for my $file (@files) {
359 63         292 $logger->info("loading annotations from file $file");
360 63   50     1161 my $fh = IO::File->new($file) || die "Can't open $file: $!" ;
361 63         6765 my @lines = $fh->getlines ;
362 63         7271 $fh->close;
363 63         1955 $root_obj->load_pod_annotation(join('',@lines)) ;
364              
365 63         78613 my @headers ;
366 63         155 foreach my $l (@lines) {
367 981 100 100     3166 if ($l =~ /^\s*#/ or $l =~ /^\s*$/){
368 918         1938 push @headers, $l
369             }
370             else {
371 63         122 last;
372             }
373             }
374 63         128 my $rel_file = $file ;
375 63         351 $rel_file =~ s/^$dir\/?//;
376 63         2178 $self->{header}{$rel_file} = \@headers;
377             }
378              
379 7         2189 return $self->{map} = \%class_file_map ;
380             }
381              
382             # can be removed end of 2019 (after buster is released)
383             sub upgrade_model {
384 123     123 0 363 my ($self, $config_class_name, $model) = @_ ;
385              
386 123         231 my $multi_backend = 0;
387 123         282 foreach my $config (qw/read_config write_config/) {
388 246         488 my $ref = $model->{$config};
389 246 50 66     693 if ($ref and ref($ref) eq 'ARRAY') {
390 0 0       0 if (@$ref == 1) {
    0          
391 0         0 $model->{$config} = $ref->[0];
392             }
393             elsif (@$ref > 1){
394 0         0 $logger->warn("$config_class_name $config: cannot migrate multiple backends to rw_config");
395 0         0 $multi_backend++;
396             }
397             }
398             }
399              
400 123 100 66     380 if ($model->{read_config} and not $multi_backend) {
401 3         104 say ("Model $config_class_name: moving read_config specification to rw_config");
402 3         15 $model->{rw_config} = delete $model->{read_config};
403             }
404              
405 123 100 66     376 if ($model->{write_config} and not $multi_backend) {
406 3         33 say "Model $config_class_name: merging write_config specification in rw_config";
407 3 50       10 if (not $multi_backend) {
408 3         4 map {$model->{rw_config}{$_} = $model->{write_config}{$_} } keys %{$model->{write_config}} ;
  9         20  
  3         14  
409 3         11 delete $model->{write_config};
410             }
411             }
412             }
413              
414             # internal
415             sub get_perl_data_model{
416 66     66 0 3269680 my $self = shift ;
417 66         290 my %args = @_ ;
418 66         206 my $root_obj = $self->{meta_root};
419             my $class_name = $args{class_name}
420 66   33     265 || croak __PACKAGE__," read: undefined class name";
421              
422 66         273 my $class_element = $root_obj->fetch_element('class') ;
423              
424             # skip if class was deleted during edition
425 66 50       4592 return unless $class_element->defined($class_name) ;
426              
427 66         1271 my $class_elt = $class_element -> fetch_with_id($class_name) ;
428              
429 66         4304 my $model = $class_elt->dump_as_data ;
430              
431             # now apply some translation to read model
432             # - Do NOT translate legacy warp parameters
433             # - Do not compact elements name
434              
435             # don't forget to add name
436 66 50       5885421 $model->{name} = $class_name if keys %$model;
437              
438 66         419 return $model ;
439             }
440              
441             sub write_app_files {
442 4     4 0 11 my $self = shift;
443              
444 4         19 my $app_dir = $self->cm_lib_dir;
445 4         29 my $app_obj = $self->meta_root->fetch_element('application');
446              
447 4         761 foreach my $app_name ( $app_obj->fetch_all_indexes ) {
448 2         65 my $app = $app_obj->fetch_with_id($app_name);
449 2         128 my $cat_dir_name = $app->fetch_element_value( name =>'category' ).'.d';
450 2         556 $app_dir->child($cat_dir_name)->mkpath();
451 2         335 my $app_file = $app_dir->child($cat_dir_name)->child($app->index_value) ;
452              
453 2         114 my @lines ;
454 2         10 foreach my $name ( $app->children ) {
455 20 100       406 next if $name eq 'category'; # saved as directory above
456              
457 18         63 my $v = $app->fetch_element_value($name); # need to spit out 0 ?
458 18 100       9433 next unless defined $v;
459 4         18 push @lines, "$name = $v\n";
460              
461             }
462 2         22 $logger->info("writing file ".$app_file);
463 2         46 $app_file->spew(@lines);
464             }
465              
466             }
467              
468             sub write_all {
469 4     4 1 2257021 my $self = shift ;
470 4         15 my %args = @_ ;
471 4         24 my $root_obj = $self->meta_root ;
472 4         33 my $dir = $self->model_dir ;
473              
474 4 50       20 croak "write_all: unexpected parameters ",join(' ', keys %args) if %args ;
475              
476 4         25 $self->write_app_files;
477              
478 4         1362 my $map = $self->{map} ;
479              
480 4         39 $dir->mkpath;
481              
482             # get list of all classes loaded by the editor
483             my %loaded_classes
484 4         296 = map { ($_ => 1); }
  65         497  
485             $root_obj->fetch_element('class')->fetch_all_indexes ;
486              
487             # remove classes that are listed in map
488 4         32 foreach my $file (keys %$map) {
489 20         36 foreach my $class_name (@{$map->{$file}}) {
  20         53  
490 40         90 delete $loaded_classes{$class_name} ;
491             }
492             }
493              
494             # add remaining classes in map
495             my %new_map = map {
496 4         19 my $f = $_;
  25         31  
497 25         56 $f =~ s!::!/!g;
498 25         72 ("$f.pl" => [ $_ ]) ;
499             } keys %loaded_classes ;
500              
501 4         37 my %map_to_write = (%$map,%new_map) ;
502              
503 4         21 foreach my $file (keys %map_to_write) {
504 45         279265 $logger->info("checking model file $file");
505              
506 45         461 my @data ;
507             my @notes ;
508 45         134 my $file_needs_write = 0;
509              
510             # check if any a class of a file was modified
511 45         122 foreach my $class_name (@{$map_to_write{$file}}) {
  45         201  
512 65 50       386 $file_needs_write++ if $self->class_needs_write($class_name);
513 65         1624 $logger->info("file $file class $class_name needs write ",$file_needs_write);
514             }
515              
516 45 50       457 next unless $file_needs_write ;
517              
518 45         109 foreach my $class_name (@{$map_to_write{$file}}) {
  45         139  
519 65         413 $logger->info("writing class $class_name");
520 65         713 my $model
521             = $self-> get_perl_data_model(class_name => $class_name) ;
522 65 50 33     733 push @data, $model if defined $model and keys %$model;
523              
524 65         668 my $node = $self->{meta_root}->grab("class:".$class_name) ;
525 65         29442 push @notes, $node->dump_annotations_as_pod ;
526             # remove class name from above list
527 65         5097666 delete $loaded_classes{$class_name} ;
528             }
529              
530 45 50       259 next unless @data ; # don't write empty model
531              
532 45         390 write_model_file ($dir->child($file), $self->{header}{$file}, \@notes, \@data);
533             }
534              
535 4         799 $self->meta_instance->clear_changes ;
536             }
537              
538             sub write_model_plugin {
539 2     2 1 4813 my $self = shift ;
540 2         19 my %args = @_ ;
541             my $plugin_dir = delete $args{plugin_dir}
542 2   33     17 || croak __PACKAGE__," write_model_plugin: undefined plugin_dir";
543             my $plugin_name = delete $args{plugin_name}
544 2   33     19 || croak __PACKAGE__," write_model_plugin: undefined plugin_name";
545 2 50       13 croak "write_model_plugin: unexpected parameters ",join(' ', keys %args) if %args ;
546              
547 2         38 my $model = $self->meta_root->dump_as_data(mode => 'custom') ;
548             # print (Dumper( $model)) ;
549              
550 2 50       2279092 my @raw_data = @{$model->{class} || []} ;
  2         20  
551 2         11 while (@raw_data) {
552 4         120 my ( $class , $data ) = splice @raw_data,0,2 ;
553 4         18 $data ->{name} = $class ;
554              
555             # does not distinguish between notes from underlying model or snipper notes ...
556 4         58 my @notes = $self->meta_root->grab("class:$class")->dump_annotations_as_pod ;
557 4         769928 my $plugin_file = $class.'.pl';
558 4         24 $plugin_file =~ s!::!/!g;
559 4         35 write_model_file ("$plugin_dir/$plugin_name/$plugin_file", [], \@notes, [ $data ]);
560             }
561              
562 2         120 $self->meta_instance->clear_changes ;
563             }
564              
565             sub read_model_plugin {
566 1     1 1 777 my $self = shift ;
567 1         9 my %args = @_ ;
568             my $plugin_dir = delete $args{plugin_dir}
569 1   33     9 || croak __PACKAGE__," write_model_plugin: undefined plugin_dir";
570             my $plugin_name = delete $args{plugin_name}
571 1   33     7 || croak __PACKAGE__," read_model_plugin: undefined plugin_name";
572              
573 1 50       6 croak "read_model_plugin: unexpected parameters ",join(' ', keys %args) if %args ;
574              
575 1         6 my @files ;
576             my $wanted = sub {
577 5     5   23 my $n = $File::Find::name ;
578 5 50 66     559 push @files, $n if (-f $_ and not /~$/
      66        
      33        
      33        
579             and $n !~ /CVS/
580             and $n !~ m!.(svn|orig|pod)$!
581             and $n =~ m!\.d/$plugin_name!
582             ) ;
583 1         13 } ;
584 1         140 find ($wanted, $plugin_dir ) ;
585              
586 1         22 my $class_element = $self->meta_root->fetch_element('class') ;
587              
588 1         157 foreach my $load_file (@files) {
589 2         653 $logger->info("trying to read plugin $load_file");
590              
591 2 50 33     81 $load_file = "./$load_file" if $load_file !~ m!^/! and -e $load_file;
592              
593 2         1134 my $plugin = do $load_file ;
594              
595 2 50       20 unless ($plugin) {
596 0 0       0 if ($@) {die "couldn't parse $load_file: $@"; }
  0 0       0  
597 0         0 elsif (not defined $plugin) {die "couldn't do $load_file: $!"}
598 0         0 else { die "couldn't run $load_file" ;}
599             }
600              
601             # there should be only only class in each plugin file
602 2         11 foreach my $model (@$plugin) {
603 2         9 my $class_name = delete $model->{name} ;
604             # load with a array ref to avoid warnings about missing order
605 2         23 $class_element->fetch_with_id($class_name)->load_data( $model ) ;
606             }
607              
608             # load annotations
609 2         4795 $logger->info("loading annotations from plugin file $load_file");
610 2   50     48 my $fh = IO::File->new($load_file) || die "Can't open $load_file: $!" ;
611 2         378 my @lines = $fh->getlines ;
612 2         226 $fh->close;
613 2         76 $self->meta_root->load_pod_annotation(join('',@lines)) ;
614             }
615             }
616              
617              
618             #
619             # New subroutine "write_model_file" extracted - Mon Mar 12 13:38:29 2012.
620             #
621             sub write_model_file {
622 49     49 0 3191 my $wr_file = shift;
623 49         139 my $comments = shift ;
624 49         125 my $notes = shift;
625 49         104 my $data = shift;
626              
627 49         1056 my $wr_dir = dirname($wr_file);
628 49 100       3731 unless ( -d $wr_dir ) {
629 8 50       1279 mkpath( $wr_dir, 0, 0755 ) || die "Can't mkpath $wr_dir:$!";
630             }
631              
632 49   33     588 my $wr = IO::File->new( $wr_file, '>' )
633             || croak "Cannot open file $wr_file:$!" ;
634 49         8752 $logger->info("in $wr_file");
635              
636 49         1344 my $dumper = Data::Dumper->new( [ \@$data ] );
637 49         2408 $dumper->Indent(1); # avoid too deep indentation
638 49         861 $dumper->Terse(1); # allow unnamed variables in dump
639 49         442 $dumper->Sortkeys(1); # sort keys in hash
640              
641 49         430 my $dump = $dumper->Dump;
642              
643             # munge pod text embedded in values to avoid spurious pod formatting
644 49         20436 $dump =~ s/\n=/\n'.'=/g;
645              
646 49         356 $wr->print(@$comments) ;
647 49         811 $wr->print( $dump, ";\n\n" );
648              
649 49         1224 $wr->print( join( "\n", @$notes ) );
650              
651 49         452 $wr->close;
652              
653             }
654              
655              
656              
657             sub list_class_element {
658 1     1 1 390 my $self = shift ;
659 1   50     9 my $pad = shift || '' ;
660              
661 1         3 my $res = '';
662 1         7 my $meta_class = $self->{meta_root}->fetch_element('class') ;
663 1         76 foreach my $class_name ($meta_class->fetch_all_indexes ) {
664 20         113 $res .= $self->list_one_class_element($class_name) ;
665             }
666 1         22 return $res ;
667             }
668              
669             sub list_one_class_element {
670 41     41 0 75 my $self = shift ;
671 41   50     113 my $class_name = shift || return '' ;
672 41   100     139 my $pad = shift || '' ;
673              
674 41         131 my $res = $pad."Class: $class_name\n";
675 41         143 my $meta_class = $self->{meta_root}->fetch_element('class')
676             -> fetch_with_id($class_name) ;
677              
678 41         4370 my @elts = $meta_class->fetch_element('element')->fetch_all_indexes ;
679              
680 41         3725 my @include = $meta_class->fetch_element('include')->fetch_all_values ;
681 41         70737 my $inc_after = $meta_class->grab_value('include_after') ;
682              
683 41 100 100     122861 if (@include and not defined $inc_after) {
684 10         30 map { $res .= $self->list_one_class_element($_,$pad.' ') ;} @include ;
  10         51  
685             }
686              
687 41 100       156 return $res unless @elts ;
688              
689 39         97 foreach my $elt_name ( @elts) {
690 392         1573 my $type = $meta_class->grab_value("element:$elt_name type") ;
691              
692 392         300266 $res .= $pad." - $elt_name ($type)\n";
693 392 100 100     1934 if (@include and defined $inc_after and $inc_after eq $elt_name) {
      100        
694 8         24 map { $res .=$self->list_one_class_element($_,$pad.' ') ;} @include ;
  11         54  
695             }
696             }
697 39         373 return $res ;
698             }
699              
700              
701             sub get_dot_diagram {
702 1     1 1 395 my $self = shift ;
703 1         3 my $dot = "digraph model {\n" ;
704              
705 1         5 my $meta_class = $self->{meta_root}->fetch_element('class') ;
706 1         70 foreach my $class_name ($meta_class->fetch_all_indexes ) {
707 20         109 my $d_class = $class_name ;
708 20         99 $d_class =~ s/::/__/g;
709              
710 20         58 my $elt_list = '';
711 20         44 my $use = '';
712              
713 20         123 my $class_obj = $self->{meta_root}->grab(qq!class:"$class_name"!);
714 20         7709 my @elts = $class_obj ->grab(qq!element!) ->fetch_all_indexes ;
715 20         6198 foreach my $elt_name ( @elts ) {
716 125         319 my $of = '';
717 125         817 my $elt_obj = $class_obj->grab(qq!element:"$elt_name"!) ;
718 125         50440 my $type = $elt_obj->grab_value("type") ;
719 125 100       67989 if ($type =~ /^list|hash$/) {
720 30         113 my $cargo = $elt_obj->grab("cargo");
721 30         8874 my $ct = $cargo->grab_value("type") ;
722 30         16186 $of = " of $ct" ;
723 30         142 $use .= $self->scan_used_class($d_class,$elt_name,$cargo);
724             }
725             else {
726 95         515 $use .= $self->scan_used_class($d_class,$elt_name,$elt_obj);
727             }
728 125         988 $elt_list .= "- $elt_name ($type$of)\\n";
729             }
730              
731 20         174 $dot .= $d_class
732             . qq! [shape=box label="$class_name\\n$elt_list"];\n!
733             . $use . "\n";
734              
735 20         107 $dot .= $self->scan_includes($class_name, $class_obj) ;
736             }
737              
738 1         5 $dot .="}\n";
739              
740 1         13 return $dot ;
741             }
742              
743             sub scan_includes {
744 20     20 0 101 my ($self,$class_name, $class_obj) = @_ ;
745 20         67 my $d_class = $class_name ;
746 20         130 $d_class =~ s/::/__/g;
747              
748 20         121 my @includes = $class_obj->grab('include')->fetch_all_values ;
749 20         48595 my $dot = '';
750 20         86 foreach my $c (@includes) {
751 13         392 say "$class_name includes $c";
752 13         45 my $t = $c;
753 13         77 $t =~ s/::/__/g;
754 13         66 $dot.= qq!$d_class -> $t ;\n!;
755             }
756 20         145 return $dot;
757             }
758              
759             sub scan_used_class {
760 125     125 0 533 my ($self,$d_class,$elt_name, $elt_obj) = @_ ;
761              
762             # define leaf call back
763             my $disp_leaf = sub {
764 2923     2923   22667636 my ($scanner, $data_ref, $node,$element_name,$index, $leaf_object) = @_ ;
765 2923 100       12411 return unless $element_name eq 'config_class_name';
766 28         129 my $v = $leaf_object->fetch;
767 28 100       79816 return unless $v;
768 21         159 $v =~ s/::/__/g;
769 21         312 $$data_ref .= qq!$d_class -> $v !
770             . qq![ style=dashed, label="$elt_name" ];\n!;
771 125         925 } ;
772              
773             # simple scanner, (print all values)
774 125         893 my $scan = Config::Model::ObjTreeScanner-> new (
775             leaf_cb => $disp_leaf, # only mandatory parameter
776             ) ;
777              
778 125         24580 my $result = '' ;
779 125         600 $scan->scan_node(\$result, $elt_obj) ;
780 125         72798 return $result ;
781             }
782              
783             __PACKAGE__->meta->make_immutable;
784              
785             1;
786              
787              
788             # ABSTRACT: Model editor for Config::Model
789              
790             __END__