File Coverage

blib/lib/Config/Model/Itself.pm
Criterion Covered Total %
statement 360 385 93.5
branch 55 100 55.0
condition 53 96 55.2
subroutine 35 39 89.7
pod 6 17 35.2
total 509 637 79.9


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.011';
12 8     8   2134696 use Mouse ;
  8         24  
  8         50  
13 8     8   2858 use Config::Model 2.103;
  8         149  
  8         255  
14 8     8   110 use 5.010;
  8         33  
15              
16 8     8   42 use IO::File ;
  8         21  
  8         991  
17 8     8   47 use Log::Log4perl 1.11;
  8         98  
  8         45  
18 8     8   337 use Carp ;
  8         16  
  8         362  
19 8     8   40 use Data::Dumper ;
  8         16  
  8         278  
20 8     8   40 use File::Find ;
  8         16  
  8         303  
21 8     8   38 use File::Path ;
  8         45  
  8         285  
22 8     8   65 use File::Basename ;
  8         16  
  8         359  
23 8     8   3122 use Data::Compare ;
  8         63757  
  8         47  
24 8     8   23128 use Path::Tiny 0.062;
  8         152  
  8         331  
25 8     8   47 use Mouse::Util::TypeConstraints;
  8         23  
  8         70  
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   20 my $self = shift;
148 9         55 my $md = $self->cm_lib_dir->child('models');
149 9         366 $md->mkpath;
150 9         1273 return $md;
151             }
152              
153             sub BUILD {
154 9     9 1 27 my $self = shift;
155              
156             my $cb = sub {
157 3911     3911   49560106 my %args = @_ ;
158 3911   50     13351 my $p = $args{path} || '' ;
159 3911 100       18234 return unless $p =~ /^class/ ;
160 3908 50       9476 return unless $args{index}; # may be empty when class order is changed
161 3908 100       13370 return if $self->class_was_changed($args{index}) ;
162 859         15266 $logger->info("class $args{index} was modified");
163              
164 859         8771 $self->add_modified_class($args{index}) ;
165 9         51 } ;
166 9         97 $self->meta_instance -> on_change_cb($cb) ;
167              
168             }
169              
170             sub add_tracked_class {
171 123     123 0 210 my $self = shift;
172 123         359 $self->set_class(shift,0) ;
173             }
174              
175             sub add_modified_class {
176 982     982 0 7773 my $self = shift;
177 982         3286 $self->set_class(shift,1) ;
178             }
179              
180             sub class_needs_write {
181 65     65 0 162 my $self = shift;
182 65         151 my $name = shift;
183 65   66     731 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 17 my $self = shift;
188 7   100     30 my $force_load = shift || 0;
189 7         16 my $read_from = shift ;
190 7         31 my $application = shift ;
191              
192 7   33     69 my $app_dir = $read_from || $self->model_dir->parent;
193 7         553 my %apps;
194 7         29 $logger->info("reading app files from ".$app_dir);
195 7         131 foreach my $dir ( $app_dir->children(qr/\.d$/) ) {
196              
197 3         391 $logger->info("reading app dir ".$dir);
198 3         43 foreach my $file ( $dir->children() ) {
199 3 50       252 next if $file =~ m!/README!;
200 3 50       25 next if $file =~ /(~|\.bak|\.orig)$/;
201 3 50 33     29 next if $application and $file->basename ne $application;
202              
203             # bad categories are filtered by the model
204 3         15 my %data = ( category => $dir->basename('.d') );
205 3         219 $logger->info("reading app file ".$file);
206              
207 3         100 foreach ($file->lines({ chomp => 1})) {
208 6         572 s/^\s+//;
209 6         21 s/\s+$//;
210 6         14 s/#.*//;
211 6         30 my ( $k, $v ) = split /\s*=\s*/;
212 6 50       19 next unless $v;
213 6         19 $data{$k} = $v;
214             }
215              
216 3         16 my $appli = $file->basename;
217 3         77 $apps{$appli} = $data{model} ;
218              
219 3 50       88 $self->meta_root->load_data(
220             data => { application => { $appli => \%data } },
221             check => $force_load ? 'no' : 'yes'
222             ) ;
223             }
224             }
225              
226 7         150801 return \%apps;
227             }
228              
229             sub read_all {
230 7     7 0 93 my $self = shift ;
231 7         28 my %args = @_ ;
232              
233 7   100     48 my $force_load = delete $args{force_load} || 0 ;
234 7         17 my $read_from ;
235             my $model_dir ;
236 7 50       33 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         34 my $apps = $self-> read_app_files($force_load, $read_from, delete $args{application});
244              
245 7   50     39 my $root_model_arg = delete $args{root_model} || '';
246 7   33     45 my $model = $apps->{$root_model_arg} || $root_model_arg ;
247 7         20 my $legacy = delete $args{legacy} ;
248              
249 7 50       68 croak "read_all: unexpected parameters ",join(' ', keys %args) if %args ;
250              
251 7         37 my $dir = $self->model_dir;
252 7         38 $dir->mkpath ;
253              
254 7         522 my $root_model_file = $model ;
255 7         24 $root_model_file =~ s!::!/!g ;
256 7   33     69 my $read_dir = $model_dir || $dir;
257 7         31 $logger->info("searching model files in ".$read_dir);
258              
259 7         109 my @files ;
260             my $wanted = sub {
261 103 50 100 103   10955 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         41 } ;
266 7         57 $read_dir->visit($wanted, { recurse => 1} ) ;
267              
268 7         398 my $i = $self->meta_instance ;
269              
270 7         44 my %read_models ;
271             my %pod_data ;
272 7         0 my %class_file_map ;
273              
274 7         0 my @all_models;
275 7         22 for my $file (@files) {
276 63         296 $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       927 my @legacy = $legacy ? ( legacy => $legacy ) : () ;
282 63         562 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         14169 my @models = $tmp_model -> load ( 'Tmp' , $file->absolute ) ;
287 63         254544 push @all_models, @models;
288              
289 63         147 my $rel_file = $file ;
290 63         303 $rel_file =~ s/^$read_dir\/?//;
291 63 50       1079 die "wrong reg_exp" if $file eq $rel_file ;
292 63         426 $class_file_map{$rel_file} = \@models ;
293              
294             # - move experience, description and level status into parameter info.
295 63         147 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         2192 my $new_model = $tmp_model -> get_model( $model_name ) ;
299              
300             # track read class to identify later classes added by user
301 123         26712 $self->add_tracked_class($model_name);
302              
303             # some modifications may be done to cope with older model styles. If a modif
304             # was done, mark the class as changed so it will be saved later
305 123 50       5123 $self->add_modified_class($model_name) unless Compare($raw_model, $new_model) ;
306              
307 123         4565 foreach my $item (qw/description summary level experience status/) {
308 615         846 foreach my $elt_name (keys %{$new_model->{element}}) {
  615         1420  
309 3825         5752 my $moved_data = delete $new_model->{$item}{$elt_name} ;
310 3825 50       7633 next unless defined $moved_data ;
311 0         0 $new_model->{element}{$elt_name}{$item} = $moved_data ;
312             }
313 615         1248 delete $new_model->{$item} ;
314             }
315              
316             # Since accept specs and elements are stored in a ordered hash,
317             # load_data expects a array ref instead of a hash ref.
318             # Build this array ref taking the order into
319             # account
320 123         221 foreach my $what (qw/element accept/) {
321 246         508 my $list = delete $new_model -> {$what.'_list'} ;
322 246         419 my $h = delete $new_model -> {$what} ;
323 246         441 $new_model -> {$what} = [] ;
324             map {
325 246         521 push @{$new_model->{$what}}, $_, $h->{$_}
  771         1046  
  771         1923  
326             } @$list ;
327             }
328              
329             # remove hash key with undefined values
330 123         374 map { delete $new_model->{$_} unless defined $new_model->{$_}
331 354 50 33     1778 and $new_model->{$_} ne ''
332             } keys %$new_model ;
333 123         3261 $read_models{$model_name} = $new_model ;
334             }
335              
336             }
337              
338 7   33     88 $self->{root_model} = $model || (sort @all_models)[0];
339              
340             # Create all classes listed in %read_models to avoid problems with
341             # include statement while calling load_data
342 7         38 my $root_obj = $self->meta_root ;
343 7         54 my $class_element = $root_obj->fetch_element('class') ;
344 7         100875 map { $class_element->fetch_with_id($_) } sort keys %read_models ;
  123         146779  
345              
346             #require Tk::ObjScanner; Tk::ObjScanner::scan_object(\%read_models) ;
347              
348 7         5554 $logger->info("loading all extracted data in Config::Model::Itself");
349             # load with a array ref to avoid warnings about missing order
350 7 100       172 $root_obj->load_data(
351             data => {class => [ %read_models ] },
352             check => $force_load ? 'no' : 'yes'
353             ) ;
354              
355             # load annotations and comment header
356 7         73688 for my $file (@files) {
357 63         260 $logger->info("loading annotations from file $file");
358 63   50     1025 my $fh = IO::File->new($file) || die "Can't open $file: $!" ;
359 63         6076 my @lines = $fh->getlines ;
360 63         7872 $fh->close;
361 63         2225 $root_obj->load_pod_annotation(join('',@lines)) ;
362              
363 63         69385 my @headers ;
364 63         155 foreach my $l (@lines) {
365 981 100 100     3319 if ($l =~ /^\s*#/ or $l =~ /^\s*$/){
366 918         1743 push @headers, $l
367             }
368             else {
369 63         114 last;
370             }
371             }
372 63         123 my $rel_file = $file ;
373 63         296 $rel_file =~ s/^$dir\/?//;
374 63         1663 $self->{header}{$rel_file} = \@headers;
375             }
376              
377 7         1824 return $self->{map} = \%class_file_map ;
378             }
379              
380             # internal
381             sub get_perl_data_model{
382 66     66 0 2192776 my $self = shift ;
383 66         287 my %args = @_ ;
384 66         202 my $root_obj = $self->{meta_root};
385             my $class_name = $args{class_name}
386 66   33     285 || croak __PACKAGE__," read: undefined class name";
387              
388 66         281 my $class_element = $root_obj->fetch_element('class') ;
389              
390             # skip if class was deleted during edition
391 66 50       4335 return unless $class_element->defined($class_name) ;
392              
393 66         1155 my $class_elt = $class_element -> fetch_with_id($class_name) ;
394              
395 66         3973 my $model = $class_elt->dump_as_data ;
396              
397             # now apply some translation to read model
398             # - Do NOT translate legacy warp parameters
399             # - Do not compact elements name
400              
401             # don't forget to add name
402 66 50       5815678 $model->{name} = $class_name if keys %$model;
403              
404 66         381 return $model ;
405             }
406              
407             sub write_app_files {
408 4     4 0 9 my $self = shift;
409              
410 4         18 my $app_dir = $self->cm_lib_dir;
411 4         20 my $app_obj = $self->meta_root->fetch_element('application');
412              
413 4         575 foreach my $app_name ( $app_obj->fetch_all_indexes ) {
414 2         56 my $app = $app_obj->fetch_with_id($app_name);
415 2         105 my $cat_dir_name = $app->fetch_element_value( name =>'category' ).'.d';
416 2         607 $app_dir->child($cat_dir_name)->mkpath();
417 2         638 my $app_file = $app_dir->child($cat_dir_name)->child($app->index_value) ;
418              
419 2         129 my @lines ;
420 2         11 foreach my $name ( $app->children ) {
421 20 100       439 next if $name eq 'category'; # saved as directory above
422              
423 18         57 my $v = $app->fetch_element_value($name); # need to spit out 0 ?
424 18 100       10172 next unless defined $v;
425 4         18 push @lines, "$name = $v\n";
426              
427             }
428 2         20 $logger->info("writing file ".$app_file);
429 2         64 $app_file->spew(@lines);
430             }
431              
432             }
433              
434             sub write_all {
435 4     4 1 1991637 my $self = shift ;
436 4         14 my %args = @_ ;
437 4         19 my $root_obj = $self->meta_root ;
438 4         29 my $dir = $self->model_dir ;
439              
440 4 50       23 croak "write_all: unexpected parameters ",join(' ', keys %args) if %args ;
441              
442 4         24 $self->write_app_files;
443              
444 4         1220 my $map = $self->{map} ;
445              
446 4         40 $dir->mkpath;
447              
448             # get list of all classes loaded by the editor
449             my %loaded_classes
450 4         270 = map { ($_ => 1); }
  65         438  
451             $root_obj->fetch_element('class')->fetch_all_indexes ;
452              
453             # remove classes that are listed in map
454 4         28 foreach my $file (keys %$map) {
455 20         31 foreach my $class_name (@{$map->{$file}}) {
  20         44  
456 40         83 delete $loaded_classes{$class_name} ;
457             }
458             }
459              
460             # add remaining classes in map
461             my %new_map = map {
462 4         15 my $f = $_;
  25         38  
463 25         61 $f =~ s!::!/!g;
464 25         81 ("$f.pl" => [ $_ ]) ;
465             } keys %loaded_classes ;
466              
467 4         32 my %map_to_write = (%$map,%new_map) ;
468              
469 4         19 foreach my $file (keys %map_to_write) {
470 45         8455 $logger->info("checking model file $file");
471              
472 45         525 my @data ;
473             my @notes ;
474 45         153 my $file_needs_write = 0;
475              
476             # check if any a class of a file was modified
477 45         113 foreach my $class_name (@{$map_to_write{$file}}) {
  45         364  
478 65 50       412 $file_needs_write++ if $self->class_needs_write($class_name);
479 65         1543 $logger->info("file $file class $class_name needs write ",$file_needs_write);
480             }
481              
482 45 50       554 next unless $file_needs_write ;
483              
484 45         105 foreach my $class_name (@{$map_to_write{$file}}) {
  45         140  
485 65         356 $logger->info("writing class $class_name");
486 65         768 my $model
487             = $self-> get_perl_data_model(class_name => $class_name) ;
488 65 50 33     681 push @data, $model if defined $model and keys %$model;
489              
490 65         615 my $node = $self->{meta_root}->grab("class:".$class_name) ;
491 65         27463 push @notes, $node->dump_annotations_as_pod ;
492             # remove class name from above list
493 65         4164496 delete $loaded_classes{$class_name} ;
494             }
495              
496 45 50       265 next unless @data ; # don't write empty model
497              
498 45         376 write_model_file ($dir->child($file), $self->{header}{$file}, \@notes, \@data);
499             }
500              
501 4         538 $self->meta_instance->clear_changes ;
502             }
503              
504             sub write_model_plugin {
505 2     2 1 2398 my $self = shift ;
506 2         8 my %args = @_ ;
507             my $plugin_dir = delete $args{plugin_dir}
508 2   33     9 || croak __PACKAGE__," write_model_plugin: undefined plugin_dir";
509             my $plugin_name = delete $args{plugin_name}
510 2   33     8 || croak __PACKAGE__," write_model_plugin: undefined plugin_name";
511 2 50       8 croak "write_model_plugin: unexpected parameters ",join(' ', keys %args) if %args ;
512              
513 2         18 my $model = $self->meta_root->dump_as_data(mode => 'custom') ;
514             # print (Dumper( $model)) ;
515              
516 2 50       27829135 my @raw_data = @{$model->{class} || []} ;
  2         16  
517 2         8 while (@raw_data) {
518 4         219 my ( $class , $data ) = splice @raw_data,0,2 ;
519 4         12 $data ->{name} = $class ;
520              
521             # does not distinguish between notes from underlying model or snipper notes ...
522 4         43 my @notes = $self->meta_root->grab("class:$class")->dump_annotations_as_pod ;
523 4         752058 my $plugin_file = $class.'.pl';
524 4         21 $plugin_file =~ s!::!/!g;
525 4         27 write_model_file ("$plugin_dir/$plugin_name/$plugin_file", [], \@notes, [ $data ]);
526             }
527              
528 2         177 $self->meta_instance->clear_changes ;
529             }
530              
531             sub read_model_plugin {
532 1     1 1 710 my $self = shift ;
533 1         6 my %args = @_ ;
534             my $plugin_dir = delete $args{plugin_dir}
535 1   33     5 || croak __PACKAGE__," write_model_plugin: undefined plugin_dir";
536             my $plugin_name = delete $args{plugin_name}
537 1   33     4 || croak __PACKAGE__," read_model_plugin: undefined plugin_name";
538              
539 1 50       4 croak "read_model_plugin: unexpected parameters ",join(' ', keys %args) if %args ;
540              
541 1         2 my @files ;
542             my $wanted = sub {
543 5     5   16 my $n = $File::Find::name ;
544 5 50 66     353 push @files, $n if (-f $_ and not /~$/
      66        
      33        
      33        
545             and $n !~ /CVS/
546             and $n !~ m!.(svn|orig|pod)$!
547             and $n =~ m!\.d/$plugin_name!
548             ) ;
549 1         5 } ;
550 1         82 find ($wanted, $plugin_dir ) ;
551              
552 1         9 my $class_element = $self->meta_root->fetch_element('class') ;
553              
554 1         71 foreach my $load_file (@files) {
555 2         275 $logger->info("trying to read plugin $load_file");
556              
557 2 50 33     46 $load_file = "./$load_file" if $load_file !~ m!^/! and -e $load_file;
558              
559 2         660 my $plugin = do $load_file ;
560              
561 2 50       10 unless ($plugin) {
562 0 0       0 if ($@) {die "couldn't parse $load_file: $@"; }
  0 0       0  
563 0         0 elsif (not defined $plugin) {die "couldn't do $load_file: $!"}
564 0         0 else { die "couldn't run $load_file" ;}
565             }
566              
567             # there should be only only class in each plugin file
568 2         6 foreach my $model (@$plugin) {
569 2         5 my $class_name = delete $model->{name} ;
570             # load with a array ref to avoid warnings about missing order
571 2         16 $class_element->fetch_with_id($class_name)->load_data( $model ) ;
572             }
573              
574             # load annotations
575 2         2722 $logger->info("loading annotations from plugin file $load_file");
576 2   50     22 my $fh = IO::File->new($load_file) || die "Can't open $load_file: $!" ;
577 2         164 my @lines = $fh->getlines ;
578 2         109 $fh->close;
579 2         32 $self->meta_root->load_pod_annotation(join('',@lines)) ;
580             }
581             }
582              
583              
584             #
585             # New subroutine "write_model_file" extracted - Mon Mar 12 13:38:29 2012.
586             #
587             sub write_model_file {
588 49     49 0 3373 my $wr_file = shift;
589 49         128 my $comments = shift ;
590 49         111 my $notes = shift;
591 49         112 my $data = shift;
592              
593 49         1020 my $wr_dir = dirname($wr_file);
594 49 100       4619 unless ( -d $wr_dir ) {
595 9 50       1529 mkpath( $wr_dir, 0, 0755 ) || die "Can't mkpath $wr_dir:$!";
596             }
597              
598 49   33     551 my $wr = IO::File->new( $wr_file, '>' )
599             || croak "Cannot open file $wr_file:$!" ;
600 49         10890 $logger->info("in $wr_file");
601              
602 49         1230 my $dumper = Data::Dumper->new( [ \@$data ] );
603 49         2231 $dumper->Indent(1); # avoid too deep indentation
604 49         893 $dumper->Terse(1); # allow unnamed variables in dump
605 49         443 $dumper->Sortkeys(1); # sort keys in hash
606              
607 49         453 my $dump = $dumper->Dump;
608              
609             # munge pod text embedded in values to avoid spurious pod formatting
610 49         15921 $dump =~ s/\n=/\n'.'=/g;
611              
612 49         332 $wr->print(@$comments) ;
613 49         795 $wr->print( $dump, ";\n\n" );
614              
615 49         1491 $wr->print( join( "\n", @$notes ) );
616              
617 49         459 $wr->close;
618              
619             }
620              
621              
622              
623             sub list_class_element {
624 1     1 1 688 my $self = shift ;
625 1   50     10 my $pad = shift || '' ;
626              
627 1         2 my $res = '';
628 1         5 my $meta_class = $self->{meta_root}->fetch_element('class') ;
629 1         62 foreach my $class_name ($meta_class->fetch_all_indexes ) {
630 20         104 $res .= $self->list_one_class_element($class_name) ;
631             }
632 1         27 return $res ;
633             }
634              
635             sub list_one_class_element {
636 41     41 0 78 my $self = shift ;
637 41   50     128 my $class_name = shift || return '' ;
638 41   100     151 my $pad = shift || '' ;
639              
640 41         114 my $res = $pad."Class: $class_name\n";
641 41         141 my $meta_class = $self->{meta_root}->fetch_element('class')
642             -> fetch_with_id($class_name) ;
643              
644 41         4113 my @elts = $meta_class->fetch_element('element')->fetch_all_indexes ;
645              
646 41         3610 my @include = $meta_class->fetch_element('include')->fetch_all_values ;
647 41         66253 my $inc_after = $meta_class->grab_value('include_after') ;
648              
649 41 100 100     113425 if (@include and not defined $inc_after) {
650 10         31 map { $res .= $self->list_one_class_element($_,$pad.' ') ;} @include ;
  10         52  
651             }
652              
653 41 100       140 return $res unless @elts ;
654              
655 39         90 foreach my $elt_name ( @elts) {
656 394         1439 my $type = $meta_class->grab_value("element:$elt_name type") ;
657              
658 394         281125 $res .= $pad." - $elt_name ($type)\n";
659 394 100 100     2016 if (@include and defined $inc_after and $inc_after eq $elt_name) {
      100        
660 8         20 map { $res .=$self->list_one_class_element($_,$pad.' ') ;} @include ;
  11         56  
661             }
662             }
663 39         364 return $res ;
664             }
665              
666              
667             sub get_dot_diagram {
668 1     1 1 695 my $self = shift ;
669 1         3 my $dot = "digraph model {\n" ;
670              
671 1         9 my $meta_class = $self->{meta_root}->fetch_element('class') ;
672 1         86 foreach my $class_name ($meta_class->fetch_all_indexes ) {
673 20         97 my $d_class = $class_name ;
674 20         85 $d_class =~ s/::/__/g;
675              
676 20         63 my $elt_list = '';
677 20         53 my $use = '';
678              
679 20         137 my $class_obj = $self->{meta_root}->grab(qq!class:"$class_name"!);
680 20         7201 my @elts = $class_obj ->grab(qq!element!) ->fetch_all_indexes ;
681 20         6070 foreach my $elt_name ( @elts ) {
682 127         403 my $of = '';
683 127         822 my $elt_obj = $class_obj->grab(qq!element:"$elt_name"!) ;
684 127         45976 my $type = $elt_obj->grab_value("type") ;
685 127 100       63669 if ($type =~ /^list|hash$/) {
686 30         129 my $cargo = $elt_obj->grab("cargo");
687 30         8362 my $ct = $cargo->grab_value("type") ;
688 30         15458 $of = " of $ct" ;
689 30         142 $use .= $self->scan_used_class($d_class,$elt_name,$cargo);
690             }
691             else {
692 97         477 $use .= $self->scan_used_class($d_class,$elt_name,$elt_obj);
693             }
694 127         928 $elt_list .= "- $elt_name ($type$of)\\n";
695             }
696              
697 20         173 $dot .= $d_class
698             . qq! [shape=box label="$class_name\\n$elt_list"];\n!
699             . $use . "\n";
700              
701 20         89 $dot .= $self->scan_includes($class_name, $class_obj) ;
702             }
703              
704 1         6 $dot .="}\n";
705              
706 1         7 return $dot ;
707             }
708              
709             sub scan_includes {
710 20     20 0 79 my ($self,$class_name, $class_obj) = @_ ;
711 20         55 my $d_class = $class_name ;
712 20         127 $d_class =~ s/::/__/g;
713              
714 20         109 my @includes = $class_obj->grab('include')->fetch_all_values ;
715 20         46589 my $dot = '';
716 20         69 foreach my $c (@includes) {
717 13         2938 say "$class_name includes $c";
718 13         60 my $t = $c;
719 13         89 $t =~ s/::/__/g;
720 13         64 $dot.= qq!$d_class -> $t ;\n!;
721             }
722 20         151 return $dot;
723             }
724              
725             sub scan_used_class {
726 127     127 0 486 my ($self,$d_class,$elt_name, $elt_obj) = @_ ;
727              
728             # define leaf call back
729             my $disp_leaf = sub {
730 2950     2950   20553842 my ($scanner, $data_ref, $node,$element_name,$index, $leaf_object) = @_ ;
731 2950 100       11034 return unless $element_name eq 'config_class_name';
732 27         112 my $v = $leaf_object->fetch;
733 27 100       58103 return unless $v;
734 20         128 $v =~ s/::/__/g;
735 20         209 $$data_ref .= qq!$d_class -> $v !
736             . qq![ style=dashed, label="$elt_name" ];\n!;
737 127         761 } ;
738              
739             # simple scanner, (print all values)
740 127         954 my $scan = Config::Model::ObjTreeScanner-> new (
741             leaf_cb => $disp_leaf, # only mandatory parameter
742             ) ;
743              
744 127         24133 my $result = '' ;
745 127         702 $scan->scan_node(\$result, $elt_obj) ;
746 127         69739 return $result ;
747             }
748              
749             __PACKAGE__->meta->make_immutable;
750              
751             1;
752              
753              
754             # ABSTRACT: Model editor for Config::Model
755              
756             __END__