File Coverage

lib/Morpheus.pm
Criterion Covered Total %
statement 159 191 83.2
branch 69 114 60.5
condition 18 29 62.0
subroutine 11 11 100.0
pod 1 2 50.0
total 258 347 74.3


line stmt bran cond sub pod time code
1             package Morpheus;
2             {
3             $Morpheus::VERSION = '0.46';
4             }
5 4     4   140792 use strict;
  4         12  
  4         930  
6             sub morph ($;$);
7             sub export ($$;$);
8              
9             # ABSTRACT: the ultimate configuration engine
10              
11              
12             sub import ($;@) {
13 34     34   15472 my $class = shift;
14 34         171 my ($caller) = caller;
15 34         710 my $export;
16 34         139 while (@_) {
17 25         45 my $key = shift;
18 25         38 my $value = shift;
19 25 100       154 if ($key eq "-defaults") {
    100          
    50          
    50          
20 6         64 Morpheus::Defaults->import($value);
21             } elsif ($key eq "-overrides") {
22 1         10 Morpheus::Overrides->import($value);
23             } elsif ($key eq "-export") {
24 0         0 $export = $value;
25             } elsif ($key =~ /^-/) {
26 0         0 die "unknown option '$key'";
27             } else {
28 18         53 export($caller, $value, $key);
29             }
30             }
31 30   50     201 $export ||= [qw(morph)];
32 4     4   25 no strict 'refs';
  4         8  
  4         332  
33 30         218 *{"${caller}::$_"} = \&{$_} for @$export;
  30         9531  
  30         101  
34             }
35              
36 4     4   2045 use Morpheus::Defaults;
  4         41  
  4         36  
37 4     4   32 use Morpheus::Overrides;
  4         5  
  4         19  
38 4     4   3138 use Morpheus::Bootstrap;
  4         176  
  4         27  
39 4     4   25 use Morpheus::Utils;
  4         7  
  4         474  
40 4     4   22 use Morpheus::Key qw(key);
  4         5  
  4         2802  
41              
42             sub export ($$;$) {
43 19     19 0 41 my ($package, $bindings, $root) = @_;
44              
45             # bindings format:
46             # ["$X", ...]
47             # ["@X", ...]
48             # ["%X", ...]
49             # ["x" => "X", ...]
50             # ["x" => "$X", ...]
51             # ["x" => "@X", ...]
52             # ["x" => [], ...]
53              
54 19 50 66     89 die "unexpected type $bindings" unless ref $bindings eq "ARRAY" or ref $bindings eq "SCALAR";
55 19   50     50 $root ||= "";
56              
57 19 100       56 if (ref $bindings eq "SCALAR") {
58              
59 1         4 my $value = morph("$root");
60 1 50       5 die "'$root': configuration variable is not defined" unless defined $value;
61              
62 1 50       4 if (ref $value eq "GLOB") {
63 0 0       0 if (defined ${*{$value}}) {
  0         0  
  0         0  
64 0         0 $$bindings = ${*{$value}};
  0         0  
  0         0  
65             } else {
66 0         0 die "'$root': configuration variable of type \$ is not defined";
67             }
68             } else {
69 1         2 $$bindings = $value;
70             }
71              
72 1         6 return;
73             }
74              
75 18 100 66     161 $root .= "/" if $root and $root !~ m{/$};
76              
77 18         48 while (@$bindings) {
78 19         37 my $ns = shift @$bindings;
79 19 50       58 die "unexpected type $ns" if ref $ns;
80 19         29 my ($var, $type, $optional);
81 19 100       103 if ($ns =~ s/^(\??)([\$\@\%])//) {
82 17         50 ($optional, $type) = ($1, $2);
83 17         37 $var = $ns;
84             } else {
85 2         5 $var = shift @$bindings;
86 2 100       10 if (ref $var) {
87 1         34 export($package, $var, "$root$ns/");
88 1         6 next;
89             } else {
90 1 50       16 $var =~ s/^(\?)// and $optional = $1;
91 1         2 $type = '$';
92 1 50       8 $var =~ s/^([\$\@\%])// and $type = $1;
93             }
94             }
95              
96 18 50       87 die "'$var': invalid variable name" unless $var =~ /^\w+$/;
97 4     4   26 my $symbol = do { no strict 'refs'; \*{"${package}::$var"} };
  4         5  
  4         11542  
  18         24  
  18         24  
  18         87  
98              
99 18         68 my $value = morph("$root$ns");
100 18 50 33     106 die "'$root$ns': configuration variable is not defined" unless $optional or defined $value;
101              
102 18 100       63 if ($type eq '$') {
    100          
    50          
103 10 100       34 if (ref $value eq "GLOB") {
104 4 100 66     9 if (defined ${*{$value}} or $optional) {
  4         8  
  4         28  
105 2         3 *$symbol = \${*{$value}};
  2         5  
  2         18  
106             } else {
107 2         48 die "'$root$ns': configuration variable of type \$ is not defined";
108             }
109             } else {
110 6         46 *$symbol = \$value;
111             }
112             } elsif ($type eq '@') {
113 4 50       21 if (ref $value eq "ARRAY") {
    50          
    0          
114 0         0 *$symbol = \@{$value};
  0         0  
115             } elsif (ref $value eq "GLOB") {
116 4 50 33     6 if (*{$value}{ARRAY} or $optional) {
  4         24  
117 4         7 *$symbol = \@{*{$value}};
  4         7  
  4         46  
118             } else {
119 0         0 die "'$root$ns': configuration variable of type \@ is not defined";
120             }
121             } elsif ($optional) {
122 0         0 *$symbol = \@{*$symbol};
  0         0  
123             } else {
124              
125 0         0 die "'$root$ns' => '$type$var': $value is not an array or glob: " . ref $value;
126             }
127             } elsif ($type eq '%') {
128 4 50       23 if (ref $value eq "HASH") {
    50          
    0          
129 0         0 *$symbol = \%{$value};
  0         0  
130             } elsif (ref $value eq "GLOB") {
131 4 100 66     7 if (*{$value}{HASH} or $optional) {
  4         41  
132 2         5 *$symbol = \%{*{$value}};
  2         3  
  2         22  
133             } else {
134 2         48 die "'$root$ns': configuration variable of type \% is not defined";
135             }
136             } elsif ($optional) {
137 0         0 *$symbol = \%{*$symbol};
  0         0  
138             } else {
139 0         0 die "'$root$ns' => '$type$var': $value is not a hash or glob";
140             }
141             } else {
142 0         0 die "'$root$ns' => '$type$var': unsupported variable type $type";
143             }
144             }
145             }
146              
147             our $stack = {};
148             our $bootstrapped;
149             our @plugins;
150              
151             require Data::Dump if $ENV{MORPHEUS_VERBOSE};
152             our $indent = "";
153             our $source = "";
154              
155             sub morph ($;$) {
156 162     162 1 700908 my ($main_ns, $type) = @_;
157 162         459 $main_ns = key($main_ns);
158              
159 162         389 local $indent = "$indent ";
160 162 50       440 print "$indent morph($main_ns)\n" if $ENV{MORPHEUS_VERBOSE};
161              
162              
163              
164 162 100       351 unless (defined $bootstrapped) {
165             #FIXME: we just need a proper caching and its invalidation
166             # then we could always call morph("/morpheus/plugins") and omit tracking if we are boostrapped or not
167              
168 4         55 my $plugins = {
169             Bootstrap => {
170             priority => 200,
171             object => Morpheus::Bootstrap->new(),
172             },
173             };
174              
175 4         8 my $plugins_prev_set;
176 4         12 my $plugins_set = "";
177 4         19 for my $iteration (0 .. 42) {
178 22 50       62 die "bootstrap hangs" if $iteration == 42;
179 22         39 local $bootstrapped = 0;
180              
181 114         1019 @plugins =
182 114         112 map { { %{$plugins->{$_}}, name => $_ } }
  198         459  
183 114         246 sort { $plugins->{$b}->{priority} <=> $plugins->{$a}->{priority} }
184 22         74 grep { $plugins->{$_}->{priority} } keys %$plugins;
185              
186 22         58 $plugins_prev_set = $plugins_set;
187 22         42 $plugins_set = join ",", map { "$_->{object}:$_->{priority}" } @plugins;
  114         461  
188 22 100       74 last if $plugins_set eq $plugins_prev_set;
189             #FIXME: check if we hang
190              
191 18         111 $plugins = morph("/morpheus/plugins");
192             }
193 4 50       20 print "plugins: ", join (", ", map { "$_->{name}:$_->{priority}" } @plugins), "\n" if $ENV{MORPHEUS_VERBOSE};
  0         0  
194 4         22 $bootstrapped = 1;
195             }
196              
197 162   50     1272 $main_ns ||= "";
198 162         234 my $value;
199              
200 162         253 OUTER:
201             #for my $plugin (@plugins) {
202             my $prev_priority = 1000;
203 162         291 for (@plugins) {
204 1064         1247 my ($plugin, $plugin_name, $priority) = @{$_}{qw(object name priority)};
  1064         2791  
205 1064 100 100     3521 last if $priority <= 100 and $main_ns ge "/morpheus/plugins";
206              
207 1054         22210 my $priority_equal = $prev_priority == $priority;
208 1054         1286 $prev_priority = $priority;
209              
210 1054 50       2408 print " $indent * ${plugin_name}->list($main_ns)\n" if $ENV{MORPHEUS_VERBOSE};
211 1054         1407 my @list = do {
212 1054 100       4347 if ($stack->{"$plugin\0$main_ns"}) {
213 48 50       157 print " $indent - skipped\n" if $ENV{MORPHEUS_VERBOSE};
214 48         138 next;
215             }
216 1006         3247 local $stack->{"$plugin\0$main_ns"} = 1;
217 1006         3556 $plugin->list($main_ns);
218             };
219 1006 50       2579 print " $indent - done\n" if $ENV{MORPHEUS_VERBOSE};
220              
221 1006         2132 while (@list) {
222 1005         1933 my ($ns, $token) = splice @list, 0, 2;
223 1005         2372 $ns = key($ns);
224              
225 1005 50       4321 print " $indent * ${plugin_name}->get($token)\n" if $ENV{MORPHEUS_VERBOSE};
226 1005         1049 my $patch = do {
227 1005 50       3591 if ($stack->{"$plugin\0$source\0$main_ns\0$token"}) {
228 0 0       0 print " $indent - skipped\n" if $ENV{MORPHEUS_VERBOSE};
229 0         0 next;
230             }
231 1005         3361 local $stack->{"$plugin\0$source\0$main_ns\0$token"} = 1;
232 1005         1556 local $source = $main_ns;
233 1005         3155 $plugin->get($token);
234             };
235 1005 50       2462 print " $indent - done\n" if $ENV{MORPHEUS_VERBOSE};
236              
237 1005 100       2566 if ($main_ns gt $ns) {
    50          
238 950         1978 my $delta = substr($main_ns, length $ns);
239 950         4152 $delta =~ s{^/}{};
240 950         3176 $patch = adjust($patch, $delta);
241             } elsif ($main_ns le $ns) {
242 55         126 my $delta = substr($ns, length $main_ns);
243 55         271 $delta =~ s{^/}{};
244 55 100       276 $patch = normalize({ $delta => $patch }) if $delta;
245             } else {
246 0         0 die "$plugin: list('$main_ns'): '$ns' => '$token'"
247             }
248              
249 1005         4209 $value = merge($value, $patch, $priority_equal);
250             # last OUTER if defined $value and ref $value ne 'HASH' and ref $value ne 'GLOB';
251             # FIXME: actually merge now merges ARRAY and SCALAR into a GLOB. uncomment this when we get rid of globs completely
252             }
253             }
254              
255 162   100     769 $type ||= "*";
256 162 50       713 if ($type eq '$') {
    100          
    100          
    50          
257 0 0       0 if (ref $value eq "GLOB") {
258 0         0 $value = ${*{$value}};
  0         0  
  0         0  
259             }
260             } elsif ($type eq '@') {
261 1 50       4 if (ref $value eq "GLOB") {
    0          
262 1         2 $value = *{$value}{ARRAY};
  1         4  
263             } elsif (ref $value ne "ARRAY") {
264 0         0 $value = undef;
265             }
266             } elsif ($type eq '%') {
267 1 50       5 if (ref $value eq "GLOB") {
    0          
268 1         2 $value = *{$value}{HASH};
  1         4  
269             } elsif (ref $value ne "HASH") {
270 0         0 $value = undef;
271             }
272             } elsif ($type ne '*') {
273 0         0 die "invalid type value '$type'"
274             }
275              
276 162 50       399 print "$indent returns ", Data::Dump::pp($value), "\n" if $ENV{MORPHEUS_VERBOSE};
277 162         777 return $value;
278             }
279              
280             1;
281              
282             __END__