File Coverage

blib/lib/Data/Annotation.pm
Criterion Covered Total %
statement 80 104 76.9
branch 12 24 50.0
condition 4 9 44.4
subroutine 15 18 83.3
pod 5 5 100.0
total 116 160 72.5


line stmt bran cond sub pod time code
1             package Data::Annotation;
2 2     2   489369 use v5.24;
  2         6  
3 2     2   1245 use Moo;
  2         15425  
  2         17  
4 2     2   3673 use experimental qw< signatures >;
  2         4752  
  2         11  
5             { our $VERSION = '0.006' }
6              
7 2     2   1570 use Ouch qw< :trytiny_var >;
  2         5794  
  2         9  
8 2     2   1295 use Try::Catch;
  2         2979  
  2         127  
9 2     2   13 use Scalar::Util qw< blessed >;
  2         4  
  2         72  
10 2     2   916 use Data::Annotation::Chain;
  2         9  
  2         84  
11 2     2   2394 use Data::Annotation::Overlay;
  2         9  
  2         88  
12              
13 2     2   13 use namespace::clean;
  2         2  
  2         9  
14              
15             has chains => (is => 'ro');
16             has default_chain => (is => 'ro', init_arg => 'default-chain');
17             has default_retval => (is => 'ro', init_arg => 'default');
18             has description => (is => 'ro', default => '');
19             has parse_context => (is => 'ro', default => sub { return {} },
20             init_arg => 'condition-parse-context');
21              
22             # index chains by name and keep cached inflated chains in hashref
23             has _cache => (is => 'ro', default => sub { return {} });
24              
25 5     5   9 sub _chain_for ($self, $name) {
  5         9  
  5         9  
  5         8  
26 5         13 my $chains = $self->chains;
27 5         12 my $cf = $self->_cache;
28 5 50       18 ouch 404, "missing chain for '$name'" unless exists($chains->{$name});
29             $cf->{$name} //= blessed($chains->{$name}) ? $chains->{$name}
30             : Data::Annotation::Chain->new(
31             'condition-parse-context' => $self->parse_context,
32 5 50 66     152 $chains->{$name}->%*,
33             );
34             }
35              
36 10     10 1 42 sub has_chain_for ($self, $name) {
  10         15  
  10         20  
  10         16  
37 10   33     100 return defined($name) && exists($self->chains->{$name});
38             }
39              
40 0     0 1 0 sub chains_list ($self) { sort { $a cmp $b } keys($self->chains->%*) }
  0         0  
  0         0  
  0         0  
  0         0  
41              
42 0     0 1 0 sub inflate_chains ($self) {
  0         0  
  0         0  
43 0         0 $self->_chain_for($_) for $self->chains_list;
44 0         0 return $self;
45             }
46              
47 5     5 1 11 sub overlay_cloak ($self, $data, %opts) {
  5         7  
  5         8  
  5         16  
  5         8  
48 5         178 return Data::Annotation::Overlay->new(under => $data, %opts);
49             }
50              
51 5     5 1 820 sub evaluate ($self, $chain, $data) {
  5         11  
  5         11  
  5         10  
  5         9  
52 5 100       19 $chain = $self->default_chain unless $self->has_chain_for($chain);
53              
54             # cloak the input $data with an Overlay, unless it's already an
55             # overlay in which case it's used directly
56 5 50 33     26 $data = $self->overlay_cloak($data,
57             value_if_missing => '',
58             value_if_undef => '',
59             ) unless blessed($data) && $data->isa('Data::Annotation::Overlay');
60              
61 5         69 my @call_sequence;
62              
63 5     5   8 my $wrapped = sub ($name) {
  5         8  
  5         11  
64 5         7 my @stack;
65 5 50       15 push @stack, { name => $name, state => {} }
66             if $self->has_chain_for($name);
67 5         15 while (@stack) {
68 5         9 my $frame = $stack[-1];
69              
70 5         17 my $call = { chain => $frame->{name} };
71 5         8 push @call_sequence, $call;
72              
73 5         16 my $chain = $self->_chain_for($frame->{name});
74 5         2027 my ($outcome, $rname) = $chain->evaluate($frame->{state}, $data);
75 5         15 $call->{outcome} = $outcome;
76 5 100       19 $call->{rule} = defined($rname) ? "($rname)" : '';
77              
78 5 100       26 if (! defined($outcome)) {
79 1         4 $call->{next} = 'pop';
80 1         3 pop(@stack);
81 1         6 next;
82             }
83              
84             # see if there's a result, either implicit or explicit
85 4 50       12 if (ref($outcome) ne 'HASH') {
86 4         34 return $outcome;
87             }
88 0 0       0 if (exists($outcome->{result})) {
89 0         0 return $outcome->{result};
90             }
91              
92             # no result so far, we either have to goto or to call another rule
93 0         0 my $name;
94 0 0       0 if (defined($outcome->{goto})) {
    0          
95 0         0 $name = $outcome->{goto};
96 0         0 pop(@stack);
97             }
98             elsif (defined($outcome->{call})) {
99 0         0 $name = $outcome->{call};
100             }
101             else {
102 0         0 ouch 400, 'cannot process hash outcome, no result/goto/call';
103             }
104 0         0 push(@stack, { name => $name, state => {} });
105             }
106              
107             # if we get here, no chain had a response so we use the default one
108 1         6 my $retval = $self->default_retval;
109 1         5 push @call_sequence,
110             {
111             initial_chain => $name,
112             note => 'return default',
113             outcome => $retval,
114             };
115 1         5 return $retval;
116 5         54 };
117              
118 5     5   146 my $retval = try { $wrapped->($chain) }
119             catch {
120 0     0   0 my $call = $call_sequence[-1];
121 0         0 $call->{outcome} = undef;
122 0         0 $call->{next} = 'abort';
123 0         0 $call->{error} = bleep();
124 0         0 ouch 400, 'evaluation error', \@call_sequence;
125 5         37 };
126              
127 5 50       136 return ($retval, $data, \@call_sequence) if wantarray;
128 5         114 return $retval;
129             }
130              
131             1;