File Coverage

lib/UR/Object/Type/ModuleWriter.pm
Criterion Covered Total %
statement 327 475 68.8
branch 152 248 61.2
condition 33 76 43.4
subroutine 28 35 80.0
pod 0 15 0.0
total 540 849 63.6


line stmt bran cond sub pod time code
1             package UR::Object::Type::ModuleWriter; # to help the installer
2              
3             package UR::Object::Type; # hold methods for the class which cover Module Read/Write.
4              
5 266     266   1054 use strict;
  266         356  
  266         6844  
6 266     266   1066 use warnings;
  266         368  
  266         8011  
7             require UR;
8 266     266   1016 use List::MoreUtils qw(first_index);
  266         404  
  266         3218  
9 266     266   116987 use Scalar::Util qw(looks_like_number);
  266         397  
  266         25801  
10              
11             our $VERSION = "0.46"; # UR $VERSION;
12              
13             our %meta_classes;
14             our $bootstrapping = 1;
15             our @partially_defined_classes;
16             our $pwd_at_compile_time = cwd();
17              
18             sub resolve_class_description_perl {
19 8     8 0 368 my $self = $_[0];
20              
21 266     266   1109 no strict 'refs';
  266         408  
  266         10284  
22 8         11 my @isa = @{ $self->class_name . "::ISA" };
  8         27  
23 266     266   993 use strict;
  266         348  
  266         149104  
24              
25 8 50       30 unless (@isa) {
26 0         0 my @i = ${ $self->is };
  0         0  
27 0         0 my @parent_class_objects = map { UR::Object::Type->is_loaded(class_name => $_) } @i;
  0         0  
28 0 0       0 die "Parent class objects not all loaded for " . $self->class_name unless (@i == @parent_class_objects);
29 0         0 @isa = map { $_->class_name } @parent_class_objects;
  0         0  
30             }
31              
32 8 50       26 unless (@isa) {
33 0         0 my @i = ${ $self->is };
  0         0  
34 0         0 my @parent_class_objects = map { UR::Object::Type->is_loaded(class_name => $_) } @i;
  0         0  
35              
36 0 0 0     0 unless (@i and @i == @parent_class_objects) {
37 0         0 Carp::confess("No inheritance meta-data found for ( @i / @parent_class_objects)" . $self->class_name)
38             }
39              
40 0         0 @isa = map { $_->class_name } @parent_class_objects;
  0         0  
41             }
42              
43 8         23 my $class_name = $self->class_name;
44 8         62 my @parent_classes = $self->parent_class_metas;
45 8         79 my $has_table = $self->has_table;
46              
47             # For getting default values for some of the properties
48 8         29 my $class_meta_meta = UR::Object::Type->get(class_name => 'UR::Object::Type');
49              
50 8         19 my $perl = '';
51              
52 8 50 33     104 unless (@isa == 1 and $isa[0] =~ /^UR::Object|UR::Entity$/ ) {
53 8 50       57 $perl .= " is => " . (@isa == 1 ? "'@isa',\n" : pprint_arrayref(\@isa) . ",\n");
54             }
55 8 50       29 $perl .= " table_name => " . ($self->table_name ? "'" . $self->table_name . "'" : 'undef') . ",\n" if $self->data_source_id;
    100          
56 8 50       40 $perl .= " is_abstract => 1,\n" if $self->is_abstract;
57 8 50 33     86 $perl .= " er_role => '" . $self->er_role . "',\n" if ($self->er_role and ($self->er_role ne $class_meta_meta->property_meta_for_name('er_role')->default_value));
58              
59 8 100       28 if ($self->{type_has}) {
60 1         3 my @keys = qw(is_optional is);
61 1         2 my %type_has = @{$self->{type_has}};
  1         4  
62 1         2 my @type_has_names = keys %type_has;
63 1         2 my $section_src;
64 1         3 for my $name (@type_has_names) {
65 1         1 my $struct = $type_has{$name};
66 1         5 $section_src .= pprint_subsection($name, _section_lines($struct, @keys));
67             }
68 1 50       3 if ($section_src) {
69 1         4 $perl .= pprint_section('type_has', $section_src);
70             }
71             }
72              
73              
74             # Meta-property attributes
75 8         15 my @property_meta_property_names;
76 8 50       27 if ($self->{'attributes_have'}) {
77             @property_meta_property_names = sort { $self->{'attributes_have'}->{$a}->{'position_in_module_header'}
78             <=>
79 1         4 $self->{'attributes_have'}->{$b}->{'position_in_module_header'} }
80 8         14 keys %{$self->{'attributes_have'}};
  8         37  
81 8         17 my $section_src = '';
82 8         20 foreach my $meta_name ( @property_meta_property_names ) {
83 2         3 my $this_meta_struct = $self->{'attributes_have'}->{$meta_name};
84              
85             # The attributes_have structure gets propogated to subclasses, but it only needs to appear
86             # in the class definition of the most-parent class
87 2         3 my $expected_name = $class_name . '::attributes_have';
88 2 50       5 next unless ( $this_meta_struct->{'is_specified_in_module_header'} eq $expected_name);
89              
90             # We want these to appear first
91 2         2 my @this_meta_properties;
92             # skip the ones we've already done
93 2         3 my @exclude_keys = qw(is_specified_in_module_header position_in_module_header);
94 2         8 my @keys = _exclude_items([keys %$this_meta_struct], \@exclude_keys);
95              
96 2         5 $section_src .= pprint_subsection($meta_name, _section_lines($this_meta_struct, @keys));
97             }
98 8 100       20 if ($section_src) {
99 1         2 $perl .= pprint_section('attributes_have', $section_src);
100             }
101             }
102              
103 8 50       23 if (exists $self->{'first_sub_classification_method_name'}) {
104             # This gets overridden by UR::Object::Type to cache the value it finds from parent
105             # classes in __first_sub_classification_method_name, so we can't just get the
106             # property through the normal channels
107 0         0 $perl .= " first_sub_classification_method_name => '" . $self->{'first_sub_classification_method_name'} ."',\n";
108             }
109              
110             # These property names are either written in other places in this sub, or shouldn't be written out
111 8         39 my %addl_property_names = map { $_ => 1 } $self->__meta__->all_property_type_names;
  894         909  
112 8         105 my @specified = qw/is class_name table_name id_by er_role is_abstract generated data_source_id schema_name doc namespace id first_sub_classification_method_name property_metas pproperty_names id_property_metas meta_class_name id_generator valid_signals roles/;
113 8         38 delete @addl_property_names{@specified};
114 8         150 for my $property_name (sort keys %addl_property_names) {
115 426         1002 my $property_obj = $self->__meta__->property_meta_for_name($property_name);
116 426 100 100     1153 next if ($property_obj->is_calculated or $property_obj->is_delegated);
117              
118 106         1175 my $property_value = $self->$property_name;
119 106   66     398 my $default_value = $property_obj && $property_obj->default_value;
120             # If the property is set on the class object
121             # and both the value and default are numeric and numerically different,
122             # or stringly different than the default
123 266     266   1289 no warnings qw( numeric uninitialized );
  266         385  
  266         670501  
124 106 100 66     554 if ( defined $property_value and
      66        
125             ( ($property_value + 0 eq $property_value and
126             $default_value + 0 eq $default_value and
127             $property_value != $default_value)
128             or
129             ($property_value ne $default_value)
130             )
131             ) {
132             # then it should show up in the class definition
133 2         7 my $value = $self->$property_name;
134 2 100       7 if (ref $value eq 'ARRAY') {
135 1         4 $value = pprint_arrayref($value);
136             } else {
137 1         3 $value = qq('$value');
138             }
139 2         11 $perl .= " $property_name => $value,\n";
140             }
141             }
142              
143 8         56 my %properties_by_section;
144 8         67 my %id_property_names = map { $_ => 1 } $self->direct_id_property_names;
  4         8  
145 8         101 my @properties = $self->direct_property_metas;
146 8         20 foreach my $property_meta ( @properties ) {
147 25         31 my $mentioned_section = $property_meta->is_specified_in_module_header;
148 25 100       34 next unless $mentioned_section; # skip implied properites
149 21         53 ($mentioned_section) = ($mentioned_section =~ m/::(\w+)$/);
150              
151 21 100 66     85 if (($mentioned_section and $mentioned_section eq 'id_implied')
    50 66        
152             or $id_property_names{$property_meta->property_name}) {
153              
154 3         3 push @{$properties_by_section{'id_by'}}, $property_meta;
  3         4  
155              
156             } elsif ($mentioned_section) {
157 18         14 push @{$properties_by_section{$mentioned_section}}, $property_meta;
  18         26  
158              
159             } else {
160 0         0 push @{$properties_by_section{'has'}}, $property_meta;
  0         0  
161             }
162             }
163              
164 8         15 my %sections_seen;
165 8         42 my $data_source_id = $self->data_source_id;
166 8 100       31 my ($data_source) = ($data_source_id ? UR::DataSource->get($data_source_id) : undef);
167 8         27 foreach my $section ( ( 'id_by', 'has', 'has_many', 'has_optional', keys(%properties_by_section) ) ) {
168 36 100       71 next unless ($properties_by_section{$section});
169 8 100       12 next if ($sections_seen{$section});
170 4         5 $sections_seen{$section} = 1;
171              
172             # New properites (will have position_in_module_header == undef) should go at the end
173             my @properties = sort { my $pos_a = defined($a->{'position_in_module_header'})
174 36 50       42 ? $a->{'position_in_module_header'}
175             : 1000000;
176             my $pos_b = defined($b->{'position_in_module_header'})
177 36 50       39 ? $b->{'position_in_module_header'}
178             : 1000000;
179 36         33 $pos_a <=> $pos_b;
180             }
181 4         5 @{$properties_by_section{$section}};
  4         14  
182              
183 4         3 my $section_src = '';
184 4         5 foreach my $property_meta ( @properties ) {
185 21         38 my $name = $property_meta->property_name;
186 21         53 my @fields = $self->_get_display_fields_for_property(
187             $property_meta,
188             has_table => $has_table,
189             section => $section,
190             data_source => $data_source,
191             attributes_have => \@property_meta_property_names);
192              
193 21         35 $section_src .= pprint_subsection($name, @fields);
194             }
195              
196 4         9 $perl .= pprint_section($section, $section_src);
197             }
198              
199 8         95 my $unique_groups = $self->unique_property_set_hashref;
200 8 50 33     68 if ($unique_groups and keys %$unique_groups) {
201              
202 0         0 $perl .= " unique_constraints => [\n";
203 0         0 for my $unique_group_name (keys %$unique_groups) {
204 0         0 my $property_names = join(' ', sort { $a cmp $b } @{ $unique_groups->{$unique_group_name}});
  0         0  
  0         0  
205 0         0 $perl .= " { "
206             . "properties => [qw/$property_names/], "
207             . "sql => '" . $unique_group_name . "'"
208             . " },\n";
209             }
210 0         0 $perl .= " ],\n";
211             }
212              
213 8 100       41 $perl .= " schema_name => '" . $self->schema_name . "',\n" if $self->schema_name;
214 8 100       28 $perl .= " data_source => '" . $self->data_source_id . "',\n" if $self->data_source_id;
215              
216 8         14 my $print_id_generator;
217 8 50       72 if (my $id_generator = $self->id_generator) {
218 8 50 66     23 if ($self->data_source_id and $id_generator eq '-urinternal') {
    100 66        
219 0         0 $print_id_generator = 1;
220             } elsif (! $self->data_source_id and $id_generator eq '-urinternal') {
221 7         11 $print_id_generator = 0;
222             } else {
223 1         2 $print_id_generator = 1;
224             }
225 8 100       25 $perl .= " id_generator => '$id_generator',\n" if ($print_id_generator);
226             }
227              
228 8 50 33     53 if ($self->roles and @{ $self->roles }) {
  8         36  
229 0         0 $perl .= " roles => " . pprint_arrayref($self->roles) . ",\n";
230             }
231              
232 8 50       61 if (my $valid_signals = $self->valid_signals) {
233 8 50       43 if (ref($valid_signals) ne 'ARRAY') {
    100          
234 0         0 Carp::croak("The 'valid_signals' metadata for class $class_name must be an arrayref, got: ".Data::Dumper::Dumper($valid_signals));
235             } elsif (@$valid_signals) {
236 1         5 $perl .= " valid_signals => ['" . join("', '", @$valid_signals) . "'],\n";
237             }
238             }
239              
240 8         57 my $doc = $self->doc;
241 8 100       27 if (defined($doc)) {
242 1         5 $doc = Dumper($doc);
243 1         44 $doc =~ s/\$VAR1 = //;
244 1         5 $doc =~ s/;\s*$//;
245             }
246 8 100       21 $perl .= " doc => $doc,\n" if defined($doc);
247 8         151 return $perl;
248             }
249              
250             sub resolve_module_header_source {
251 7     7 0 14 my $self = shift;
252 7         27 my $class_name = $self->class_name;
253 7         22 my $perl = "class $class_name {\n";
254 7         61 $perl .= $self->resolve_class_description_perl;
255 7         17 $perl .= "};\n";
256 7         28 return $perl;
257             }
258              
259             my $next_line_prefix = "\n";
260             my $deep_indent_prefix = "\n" . (" " x 55);
261              
262             sub _get_display_fields_for_property {
263 21     21   19 my $self = shift;
264 21         12 my $property = shift;
265 21         44 my %params = @_;
266              
267 21 50       39 if (not $property->is_specified_in_module_header) {
268             # we omit showing implied properties which have no additional data,
269             # unless they have their own docs, a specified column, etc.
270 0         0 return();
271             }
272              
273 21         18 my @fields;
274 21         28 my $property_name = $property->property_name;
275              
276 21         32 my $type = $property->data_type;
277 21 100       30 if ($type) {
278 18 50       33 push @fields, "is => '$type'" if $type;
279             }
280              
281 21 50 33     31 if (defined($property->data_length) and length($property->data_length)) {
282 0         0 push @fields, "len => " . $property->data_length;
283             }
284              
285 21 50 33     36 if ($property->is_legacy_eav) {
    100          
    100          
    50          
286             # temp hack for entity attribute values
287 0         0 push @fields, "is_legacy_eav => 1";
288             }
289             elsif ($property->is_delegated) {
290             # do nothing
291             }
292             elsif ($property->is_calculated) {
293 3 100       6 if (my $calc_from = $property->calculate_from) {
294 2 50 33     46 if ($calc_from and @$calc_from == 1) {
    50          
295 0         0 push @fields, "calculate_from => '" . $calc_from->[0] . "'";
296             } elsif ($calc_from) {
297 2         6 push @fields, "calculate_from => [ '" . join("', '", @$calc_from) . "' ]";
298             }
299             }
300              
301 3         3 my $calc_source;
302 3         4 foreach my $calc_type ( qw( calculate calculate_sql calculate_perl calculate_js ) ) {
303 12 100       24 if ($property->$calc_type) {
304 3         3 $calc_source = 1;
305 3         6 push @fields, "$calc_type => q(" . $property->$calc_type . ")";
306             }
307             }
308              
309 3 50       6 push @fields, 'is_calculated => 1' unless ($calc_source);
310             }
311             elsif ($params{has_table} && ! $property->is_transient) {
312 10 50       22 unless ($property->column_name) {
313 0         0 die("no column for property on class with table: " . $property->property_name .
314             " class: " . $self->class_name . "?");
315             }
316              
317 10         12 my $ds = $params{'data_source'};
318 10   33     32 my $should_uc = ($ds && $ds->table_and_column_names_are_upper_case);
319              
320 10         15 my $cname = $property->column_name;
321 10         14 my $pname = $property->property_name;
322 10 50       14 my $expected_cname = $should_uc ? uc($pname) : $pname;
323 10 100       22 if ($cname ne $expected_cname) {
324 2         5 push @fields, "column_name => '" . $cname . "'";
325             }
326             }
327              
328 21 50       38 if (defined($property->default_value)) {
329 0         0 my $value = $property->default_value;
330 0 0       0 if (! looks_like_number($value)) {
331 0         0 $value = "'$value'";
332             }
333 0         0 push @fields, "default_value => $value";
334             }
335              
336 21 100       18 if (my @id_by = eval { $property->get_property_name_pairs_for_join }) {
  21         57  
337 3 50       7 unless (defined $property->reverse_as) {
338             push @fields, "id_by => "
339             . (@id_by > 1 ? '[ ' : '')
340 3 100       11 . join(", ", map { "'" . $_->[0] . "'" } @id_by)
  5 100       15  
341             . (@id_by > 1 ? ' ]' : '');
342             }
343              
344 3 100       8 if (defined $property->id_class_by) {
345 1         3 push @fields, sprintf("id_class_by => '%s'", $property->id_class_by);
346             }
347             }
348              
349 21 100       57 if ($property->via) {
350 3         7 push @fields, "via => '" . $property->via . "'";
351 3 100 66     9 if ($property->to and $property->to ne $property->property_name) {
352 2         6 push @fields, "to => '" . $property->to . "'";
353             }
354              
355 3 100       8 if ($property->is_mutable) {
356             # via properties are not usually mutable
357 1         2 push @fields, 'is_mutable => 1';
358             }
359              
360 3         7 my $via_property_name = $property->via;
361 3 100       6 if ($via_property_name eq '__self__') {
362 1         3 $via_property_name = $property->to;
363             }
364 3         7 my $via = $property->class_name->__meta__->properties(property_name => $via_property_name);
365 3 50       10 if (! $via) {
    100          
366             # maybe via a method?? Safer to use is_many than not
367 0         0 push @fields, 'is_many => 1';
368              
369             } elsif ($property->is_many ne $via->is_many) {
370 1         3 push @fields, 'is_many => ' . $property->is_many;
371             }
372             }
373 21 100       40 if ($property->reverse_as) {
374 2         5 push @fields, "reverse_as => '" . $property->reverse_as . "'";
375              
376 2 100       5 if ($property->is_mutable) {
377             # reverse_as properties are not usually mutable
378 1         5 push @fields, 'is_mutable => 1';
379             }
380             }
381              
382 21 50       41 if ($property->constraint_name) {
383 0         0 push @fields, "constraint_name => '" . $property->constraint_name . "'";
384             }
385              
386 21 100       30 if ($property->where) {
387 3         5 my @where_parts = ();
388              
389 3         3 my @where = @{ $property->where };
  3         6  
390 3         7 while (@where) {
391 4         5 my $prop_name = shift @where;
392 4         4 my $comparison = shift @where;
393             # wrap 'property operator' with quotes if it contains space
394 4 100       11 if (index($prop_name, ' ') >= 0) {
395 1         2 $prop_name = "'$prop_name'";
396             }
397 4 100       14 if (! ref($comparison)) {
    100          
    50          
398             # It's a strictly equals comparison.
399             # wrap it in quotes...
400 1         2 $comparison = "'$comparison'";
401              
402             } elsif (ref($comparison) eq 'HASH') {
403             # It's a more complicated operator
404 2         2 my @operator_parts = ();
405 2         6 foreach my $key ( 'operator', 'value', keys %$comparison ) {
406 8 100       15 if ($comparison->{$key}) {
407 4 50       7 if (ref($comparison->{$key})) {
408 0         0 my $class_name = $property->class_name;
409 0         0 Carp::croak("Modulewriter doesn't know how to handle property $property_name of class $class_name. Its 'where' has a non-scalar value for the '$key' key");
410             }
411 4         9 push @operator_parts, "$key => '" . delete($comparison->{$key}) . "'";
412             }
413             }
414 2         7 $comparison = '{ ' . join(', ', @operator_parts) . ' } ';
415             } elsif (ref($comparison) eq 'ARRAY') {
416 1         3 $comparison = pprint_arrayref($comparison);
417             } else {
418 0         0 my $class_name = $property->class_name;
419 0         0 Carp::croak("Modulewriter doesn't know how to handle property $property_name of class $class_name. Its 'where' is not a simple scalar, hashref, or arrayref.");
420             }
421 4         11 push @where_parts, "$prop_name => $comparison";
422             }
423 3         8 push @fields, 'where => [ ' . join(', ', @where_parts) . ' ]';
424             }
425              
426 21 100       32 if (my $valid_values_arrayref = $property->valid_values) {
427 2         6 push @fields, "valid_values => " . pprint_arrayref($valid_values_arrayref);
428             }
429              
430 21 50       35 if (my $example_values_arrayref = $property->example_values) {
431 0         0 push @fields, "example_values => " . pprint_arrayref($example_values_arrayref);
432             }
433              
434             # All the things like is_optional, is_many, etc
435             # show only true values, false is default
436             # section can be things like 'has', 'has_optional' or 'has_transient_many_optional'
437 21         24 my $section = $params{'section'};
438 21         36 $section =~ m/^has_(.*)/;
439 21   100     83 my @sections = split('_',$1 || '');
440              
441 21         30 for my $std_field_name (qw/optional abstract transient constant classwide many deprecated/) {
442 147 100       150 next if (grep { $std_field_name eq $_ } @sections); # Don't print is_optional if we're in the has_optional section
  56         96  
443 139         113 my $property_name = "is_" . $std_field_name;
444 139 100       222 push @fields, "$property_name => " . $property->$property_name if $property->$property_name;
445             }
446              
447              
448 21         16 foreach my $meta_property ( @{$params{'attributes_have'}} ) {
  21         33  
449 42         35 my $value = $property->{$meta_property};
450 42 100       57 if (defined $value) {
451 2 100       9 my $format = looks_like_number($value) ? "%s => %s" : "%s => '%s'";
452 2         8 push @fields, sprintf($format, $meta_property, $value);
453             }
454             }
455              
456 21         40 my $desc = $property->doc;
457 21 100 66     45 if ($desc && length($desc)) {
458 3         6 $desc =~ s/([\$\@\%\\\"])/\\$1/g;
459 3         5 $desc =~ s/\n/\\n/g;
460 3 100       6 if ($desc =~ /'/) {
461 1         4 $desc = "q($desc)";
462             } else {
463 2         4 $desc = "'$desc'";
464             }
465 3         5 push @fields, $next_line_prefix . "doc => $desc";
466             }
467              
468 21         65 return @fields;
469             }
470              
471             sub module_base_name {
472 756     756 0 3155 my $file_name = shift->class_name;
473 756         3004 $file_name =~ s/::/\//g;
474 756         1180 $file_name .= ".pm";
475 756         1347 return $file_name;
476             }
477              
478             sub module_path {
479 679     679 0 966 my $self = shift;
480 679         3179 my $base_name = $self->module_base_name;
481 679         1452 my $path = $INC{$base_name};
482 679 100       2571 return _abs_path_relative_to_pwd_at_compile_time($path) if $path;
483              
484 83         126 my $namespace;
485 83         174 my $first_slash = index($base_name, '/');
486 83 100       229 if ($first_slash >= 0) {
487             # Normal case...
488 55         127 $namespace = substr($base_name, 0, $first_slash);
489 55         89 $namespace .= ".pm";
490             } else {
491             # This module must _be_ the namespace
492 28         50 $namespace = $base_name;
493             }
494              
495 83         194 for my $dir (map { _abs_path_relative_to_pwd_at_compile_time($_) } grep { -d $_ } @INC) {
  990         1074  
  990         12252  
496 500 100       4644 if (-e $dir . "/" . $namespace) {
497 51         146 my $try_path = $dir . '/' . $base_name;
498 51         220 return $try_path;
499             }
500             }
501 32         95 return;
502             }
503              
504             sub _abs_path_relative_to_pwd_at_compile_time { # not a method
505 1586     1586   1650 my $path = shift;
506 1586 100       4049 if ($path !~ /^[\/\\]/) {
507 2         6 $path = $pwd_at_compile_time . '/' . $path;
508             }
509 1586         69968 my $path2 = Cwd::abs_path($path);
510 1586         3151 return $path2;
511             }
512              
513              
514             sub module_directory {
515 71     71 0 111 my $self = shift;
516 71         342 my $base_name = $self->module_base_name;
517 71         293 my $path = $self->module_path;
518 71 100 66     396 return unless defined($path) and length($path);
519 43 50       807 unless ($path =~ s/$base_name$//) {
520 0         0 Carp::confess("Failed to find base name $base_name at the end of path $path!")
521             }
522 43         139 return $path;
523             }
524              
525             sub module_data_subdirectory {
526 0     0 0 0 my $self = shift;
527 0         0 my $path = $self->module_path;
528 0         0 $path =~ s/.pm$//;
529 0         0 return $path;
530             }
531              
532              
533             sub module_source_lines {
534 0     0 0 0 my $self = shift;
535 0         0 my $file_name = $self->module_path;
536 0         0 my $in = IO::File->new("<$file_name");
537 0 0       0 unless ($in) {
538 0         0 return (undef,undef,undef);
539             }
540 0         0 my @module_src = <$in>;
541 0         0 $in->close;
542             return @module_src
543 0         0 }
544              
545             sub module_source {
546 0     0 0 0 join("",shift->module_source_lines);
547             }
548              
549             sub module_header_positions {
550 0     0 0 0 my $self = shift;
551              
552 0         0 my @module_src = $self->module_source_lines;
553 0         0 my $namespace = $self->namespace;
554 0         0 my $class_name = $self->class_name;
555              
556 0 0       0 unless ($self->namespace) {
557 0         0 die "No namespace on $self->{class_name}?"
558             }
559              
560 0 0       0 $namespace = 'UR' if $namespace eq $self->class_name;
561              
562 0         0 my $state = 'before';
563 0         0 my ($begin,$end,$use);
564 0         0 for (my $n = 0; $n < @module_src; $n++) {
565 0         0 my $line = $module_src[$n];
566 0 0       0 if ($state eq 'before') {
    0          
567 0 0 0     0 if ($line and $line =~ /^use $namespace;/) {
568 0         0 $use = $n;
569             }
570 0 0 0     0 if (
      0        
571             $line and (
572             $line =~ /^define UR::Object::Type$/
573             or $line =~ /^(App|UR)::Object::(Type|Class)->(define|create)\($/
574             or $line =~ /^class\s*$class_name\b/
575             )
576             ) {
577 0         0 $begin = $n;
578 0         0 $state = 'during';
579             }
580             else {
581              
582             }
583             }
584             elsif ($state eq 'during') {
585 0         0 my $old_meta_src .= $line; # FIXME this dosen't appear anywhere else??
586 0 0       0 if ($line =~ /^(\)|\}|);\s*$/) {
587 0         0 $end = $n;
588 0         0 $state = 'after';
589             }
590             }
591             }
592              
593             # cache
594 0         0 $self->{module_header_positions} = [$begin,$end,$use];
595              
596             # return
597 0         0 return ($begin,$end,$use);
598             }
599              
600             sub module_header_source_lines {
601 0     0 0 0 my $self = shift;
602 0         0 my ($begin,$end,$use) = $self->module_header_positions;
603 0         0 my @src = $self->module_source_lines;
604 0 0       0 return unless $end;
605 0         0 @src[$begin..$end];
606             }
607              
608             sub module_header_source {
609 0     0 0 0 join("",shift->module_header_source_lines);
610             }
611              
612             sub rewrite_module_header {
613 266     266   1620 use strict;
  266         396  
  266         5489  
614 266     266   997 use warnings;
  266         385  
  266         316109  
615              
616 0     0 0 0 my $self = shift;
617 0         0 my $package = $self->class_name;
618              
619             # generate new class metadata
620 0         0 my $new_meta_src = $self->resolve_module_header_source;
621 0 0       0 unless ($new_meta_src) {
622 0         0 die "Failed to generate source code for $package!";
623             }
624              
625 0         0 my ($begin,$end,$use) = $self->module_header_positions;
626              
627 0         0 my $namespace = $self->namespace;
628 0 0       0 $namespace = 'UR' if $namespace eq $self->class_name;
629              
630 0 0       0 unless ($namespace) {
631 0         0 ($namespace) = ($package =~ /^(.*?)::/);
632             }
633 0 0       0 $new_meta_src = "use $namespace;\n" . $new_meta_src unless $use;
634              
635             # determine the path to the module
636             # this may not exist
637 0         0 my $module_file_path = $self->module_path;
638              
639             # temp safety hack
640 0 0       0 if ($module_file_path =~ "/gsc/scripts/lib") {
641 0         0 Carp::confess("attempt to write directly to the app server!");
642             }
643              
644             # determine the new source for the module
645 0         0 my @module_src;
646             my $old_file_data;
647 0 0       0 if (-e $module_file_path) {
648             # rewrite the existing module
649              
650             # find the old positions of the module header
651 0         0 @module_src = $self->module_source_lines;
652              
653             # cleanup legacy cruft
654 0 0       0 unless ($namespace eq 'UR') {
655 0 0       0 @module_src = map { ($_ =~ m/^use UR;/?"":$_) } @module_src;
  0         0  
656             }
657              
658 0 0       0 if (!grep {$_ =~ m/^use warnings;/} @module_src) {
  0         0  
659 0         0 $new_meta_src = "use warnings;\n" . $new_meta_src;
660             }
661              
662 0 0       0 if (!grep {$_ =~ m/^use strict;/} @module_src) {
  0         0  
663 0         0 $new_meta_src = "use strict;\n" . $new_meta_src;
664             }
665              
666             # If $begin and $end are undef, then module_header_positions() didn't find any
667             # old code and we're inserting all brand new stuff. Look for the package declaration
668             # and insert after that.
669 0         0 my $len;
670 0 0 0     0 if (defined $begin || defined $end) {
671 0         0 $len = $end-$begin+1;
672             } else {
673             # is there a more fool-proof way to find it?
674 0         0 for ($begin = 0; $begin < $#module_src; ) {
675 0 0       0 last if ($module_src[$begin++] =~ m/package\s+$package;/);
676             }
677 0         0 $len = 0;
678             }
679              
680             # replace the old lines with the new source
681             # note that the inserted "row" is multi-line, but joins nicely below...
682 0         0 splice(@module_src,$begin,$len,$new_meta_src);
683              
684 0         0 my $f = IO::File->new($module_file_path);
685 0         0 $old_file_data = join('',$f->getlines);
686 0         0 $f->close();
687             }
688             else {
689             # write new module source
690              
691             # put =cut marks around it if this is a special metadata class
692             # the definition at the top is non-functional for bootstrapping reasons
693 0 0       0 if ($meta_classes{$package}) {
694 0         0 $new_meta_src = "=cut\n\n$new_meta_src\n\n=cut\n\n";
695 0         0 $self->warning_message("Meta package $package");
696             }
697              
698 0         0 @module_src = join("\n",
699             "package " . $self->class_name . ";",
700             "",
701             "use strict;",
702             "use warnings;",
703             "",
704             $new_meta_src,
705             "1;",
706             ""
707             );
708             }
709              
710 0   0     0 $ENV{'HOST'} ||= '';
711 0         0 my $temp = "$module_file_path.$$.$ENV{HOST}";
712 0         0 my $temp_dir = $module_file_path;
713 0         0 $temp_dir =~ s/\/[^\/]+$//;
714 0 0       0 unless (-d $temp_dir) {
715 0         0 print "mkdir -p $temp_dir\n";
716 0         0 system "mkdir -p $temp_dir";
717             }
718 0         0 my $out = IO::File->new(">$temp");
719 0 0       0 unless ($out) {
720 0         0 die "Failed to create temp file $temp!";
721             }
722 0         0 for (@module_src) { $out->print($_) };
  0         0  
723 0         0 $out->close;
724              
725 0         0 my $rv = system qq(perl -e 'eval `cat $temp`' 2>/dev/null 1>/dev/null);
726 0         0 $rv /= 255;
727 0 0       0 if ($rv) {
728 0         0 die "Module is not compilable with new source!";
729             }
730             else {
731 0 0       0 unless (rename $temp, $module_file_path) {
732 0         0 die "Error renaming $temp to $module_file_path!";
733             }
734             }
735              
736 0         0 UR::Context::Transaction->log_change($self, ref($self), $self->id, 'rewrite_module_header', Data::Dumper::Dumper{path => $module_file_path, data => $old_file_data});
737              
738 0         0 return 1;
739             }
740              
741              
742             sub pprint_arrayref {
743 4     4 0 7 my $arrayref = shift;
744             # Useqq(1) causes newlines to be escaped so the only newlines are those
745             # injected by Indent(1). Useqq(1) also quotes string values so we can
746             # strip the whitespace around the newlines.
747 4         39 my $value_string = Data::Dumper->new([$arrayref])->Terse(1)->Indent(1)->Useqq(1)->Dump;
748 4         320 $value_string =~ s/\s*\n\s*/ /g;
749 4         17 $value_string =~ s/\s*$//;
750 4         10 return $value_string;
751             }
752              
753              
754             sub pprint_section {
755 6     6 0 13 my ($section, $section_src) = @_;
756 6         7 my $indent_section = ' ' x 4;
757 6         41 return "$indent_section$section => [\n$section_src$indent_section],\n";
758             }
759              
760             {
761             my $indent_name = ' ' x 8;
762             my $indent_key = $indent_name . ' ' x 4;
763             my $max_width = 78;
764             sub pprint_subsection {
765 24     24 0 37 my ($name, @fields) = @_;
766              
767 24         23 foreach ( @fields ) { s/^\s+// }
  58         78  
768 24         36 my $section_src = _pprint_subsection_one_line($name, @fields);
769 24 100       143 if (length($section_src) > $max_width) {
770 12         16 $section_src = _pprint_subsection_multi_line($name, @fields);
771             }
772 24         58 return $section_src;
773             }
774              
775             sub _pprint_subsection_one_line {
776 24     24   26 my $name = shift;
777              
778 24         62 return $indent_name . $name . ' => { '
779             . join(', ', @_)
780             . " },\n";
781             }
782              
783             sub _pprint_subsection_multi_line {
784 12     12   13 my $name = shift;
785              
786 12         36 return $indent_name . $name . " => {\n"
787             . $indent_key . join(",\n$indent_key", @_) . ",\n"
788             . $indent_name . "},\n";
789             }
790             }
791              
792             sub _quoted_value {
793 9     9   1432 my $value = shift;
794 9         11 my ($qo, $qc) = ('', '');
795 9 100       31 if (!looks_like_number($value)) {
796 5 100       14 if ($value =~ /'/) {
797 1         2 ($qo, $qc) = ('q(', ')');
798             } else {
799 4         7 ($qo, $qc) = ("'", "'");
800             }
801             }
802 9         27 return "$qo$value$qc";
803             }
804              
805             sub _idx {
806 16     16   502 my $e = shift;
807 16         20 my @expected_order = qw(
808             is
809             is_optional
810             );
811 16     27   45 my $e_idx = first_index { $_ eq $e } @expected_order;
  27         23  
812 16 100       37 if ($e_idx == -1) {
813 9         8 $e_idx = scalar(@expected_order);
814             }
815 16         28 return $e_idx;
816             }
817              
818             sub _key_sorter {
819 7     7   13 my ($a_idx, $b_idx) = (_idx($a), _idx($b));
820 7         6 my $cmp;
821 7 100       12 if ($a_idx == $b_idx) {
822 3         4 $cmp = $a cmp $b;
823             } else {
824 4         5 $cmp = $a_idx <=> $b_idx;
825             }
826 7         11 return $cmp;
827             }
828              
829             sub _sort_keys {
830 4     4   261 sort _key_sorter @_;
831             }
832              
833             sub _exclude_items {
834 3     3   430 my ($list, $exclude) = @_;
835             return grep {
836 3         6 my $l = $_;
  11         9  
837             !grep {
838 11         8 my $e = $_;
  22         16  
839 22         29 $e eq $l;
840             } @$exclude;
841             } @$list;
842             }
843              
844             sub _section_lines {
845 3     3   4 my ($struct, @keys) = @_;
846 3         4 @keys = _sort_keys(@keys);
847             my @lines = map {
848 3         4 my $value = _quoted_value($struct->{$_});
  5         9  
849 5         14 sprintf('%s => %s', $_, $value);
850             } @keys;
851 3         9 return @lines;
852             }
853              
854              
855             1;
856              
857             =pod
858              
859             =head1 NAME
860              
861             UR::Object::Type::ModuleWriter - Helper module for UR::Object::Type responsible for writing Perl modules
862              
863             =head1 DESCRIPTION
864              
865             Subroutines within this module actually live in the UR::Object::Type
866             namespace; this module is just a convienent place to collect them. The
867             Module Writer is used by the class updater system (L<(UR::Namespace::Command::Update::Classes>
868             and 'ur update classes) to add, remove and alter the Perl modules behind
869             the classes within a Namespace.
870              
871             =head1 METHODS
872              
873             =over 4
874              
875             =item resolve_module_header_source
876              
877             $classobj->resolve_module_header_source();
878              
879             Returns a string that represents a fully-formed class definition the the
880             given class metaobject $classobj.
881              
882             =item resolve_class_description_perl
883              
884             $classobj->resolve_class_description_perl()
885              
886             Used by resolve_module_header_source(). This method inspects all the
887             applicable properties of the class metaobject and builds up a string that
888             gets inserted between the {...} of the class definition string.
889              
890             =item rewrite_module_header
891              
892             $classobj->rewrite_module_header();
893              
894             This method rewrites an existing Perl module file in place for the class
895             metaobject, or creates a new file if one does not already exist.
896              
897             =item module_base_name
898              
899             Returns the pathname of the class's module relative to the top level directory
900             of that class's Namespace.
901              
902             =item module_path
903              
904             Returns the fully qualified pathname of the class's module.
905              
906             =item module_source_lines
907              
908             Returns the text of the class's Perl module as a list of strings.
909              
910             =item module_source
911              
912             Returns the text of the class's Perl module as a single string.
913              
914             =item module_header_positions
915              
916             Returns a 3-element list ($begin, $end, $use) where $begin is the line number
917             where the class header begins. $end is the line number where it ends. $use
918             is the line number where the module declares that it use's a Namespace.
919              
920             =item module_header_source_lines
921              
922             Returns the text of the class's Perl module source where the class definition
923             is as a list of strings.
924              
925             =item module_header_source
926              
927             Returns the text of the class's Perl module source where the class definition
928             is as a single string.
929              
930             =back
931              
932             =head1 SEE ALSO
933              
934             UR::Object::Type, UR::Object::Type::Initializer
935              
936             =cut
937              
938