line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#====================================================================== |
2
|
|
|
|
|
|
|
package Data::Domain; # documentation at end of file |
3
|
|
|
|
|
|
|
#====================================================================== |
4
|
4
|
|
|
4
|
|
303335
|
use 5.010; |
|
4
|
|
|
|
|
41
|
|
5
|
4
|
|
|
4
|
|
21
|
use strict; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
115
|
|
6
|
4
|
|
|
4
|
|
29
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
129
|
|
7
|
4
|
|
|
4
|
|
20
|
use Carp; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
246
|
|
8
|
4
|
|
|
4
|
|
2070
|
use Data::Dumper; |
|
4
|
|
|
|
|
21047
|
|
|
4
|
|
|
|
|
243
|
|
9
|
4
|
|
|
4
|
|
2127
|
use Scalar::Does 0.007; |
|
4
|
|
|
|
|
489685
|
|
|
4
|
|
|
|
|
48
|
|
10
|
4
|
|
|
4
|
|
2257
|
use Scalar::Util (); |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
63
|
|
11
|
4
|
|
|
4
|
|
18
|
use Try::Tiny; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
218
|
|
12
|
4
|
|
|
4
|
|
2080
|
use Data::Reach qw/reach/; |
|
4
|
|
|
|
|
8732
|
|
|
4
|
|
|
|
|
18
|
|
13
|
4
|
|
|
4
|
|
2740
|
use List::MoreUtils qw/part natatime any/; |
|
4
|
|
|
|
|
40744
|
|
|
4
|
|
|
|
|
28
|
|
14
|
4
|
|
|
4
|
|
4904
|
use if $] < 5.037, experimental => 'smartmatch'; # smartmatch no longer experimental after 5.037 |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
31
|
|
15
|
4
|
50
|
|
|
|
67
|
use overload '""' => \&_stringify, |
16
|
4
|
|
|
4
|
|
16478
|
$] < 5.037 ? ('~~' => \&_matches) : (); # fully deprecated, so cannot be overloaded |
|
4
|
|
|
|
|
11
|
|
17
|
4
|
|
|
4
|
|
2255
|
use match::simple (); |
|
4
|
|
|
|
|
7662
|
|
|
4
|
|
|
|
|
627
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our $VERSION = "1.10"; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
our $MESSAGE; # global var for last message from _matches() |
22
|
|
|
|
|
|
|
our $MAX_DEEP = 100; # limit for recursive calls to inspect() |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
25
|
|
|
|
|
|
|
# exports |
26
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
27
|
|
|
|
|
|
|
|
28
|
0
|
|
|
0
|
1
|
0
|
sub node_from_path {warn "node_from_path is deprecated; use Data::Reach"; &reach} # for backwards compat |
|
0
|
|
|
|
|
0
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# lists of symbols to export |
31
|
|
|
|
|
|
|
my @CONSTRUCTORS; |
32
|
|
|
|
|
|
|
my %SHORTCUTS; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
BEGIN { |
35
|
4
|
|
|
4
|
|
22
|
@CONSTRUCTORS = qw/Whatever Empty |
36
|
|
|
|
|
|
|
Num Int Nat Date Time String Handle |
37
|
|
|
|
|
|
|
Enum List Struct One_of All_of/; |
38
|
4
|
|
|
|
|
501
|
%SHORTCUTS = ( |
39
|
|
|
|
|
|
|
True => [ -true => 1 ], |
40
|
|
|
|
|
|
|
False => [ -true => 0 ], |
41
|
|
|
|
|
|
|
Defined => [ -defined => 1 ], |
42
|
|
|
|
|
|
|
Undef => [ -defined => 0 ], |
43
|
|
|
|
|
|
|
Blessed => [ -blessed => 1 ], |
44
|
|
|
|
|
|
|
Unblessed => [ -blessed => 0 ], |
45
|
|
|
|
|
|
|
Ref => [ -ref => 1 ], |
46
|
|
|
|
|
|
|
Unref => [ -ref => 0 ], |
47
|
|
|
|
|
|
|
Regexp => [ -does => 'Regexp' ], |
48
|
|
|
|
|
|
|
Obj => [ -blessed => 1 ], |
49
|
|
|
|
|
|
|
Class => [ -package => 1 ], |
50
|
|
|
|
|
|
|
); |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# setup exports through Sub::Exporter API |
54
|
|
|
|
|
|
|
use Sub::Exporter -setup => { |
55
|
|
|
|
|
|
|
exports => [ 'node_from_path', |
56
|
56
|
|
|
|
|
101
|
(map {$_ => \&_wrap_domain } @CONSTRUCTORS ), |
57
|
4
|
|
|
|
|
17
|
(map {$_ => \&_wrap_shortcut_options} keys %SHORTCUTS) ], |
|
44
|
|
|
|
|
138
|
|
58
|
|
|
|
|
|
|
groups => { constructors => \@CONSTRUCTORS, |
59
|
|
|
|
|
|
|
shortcuts => [keys %SHORTCUTS] }, |
60
|
|
|
|
|
|
|
collectors => { INIT => \&_sub_exporter_init }, |
61
|
|
|
|
|
|
|
installer => \&_sub_exporter_installer, |
62
|
4
|
|
|
4
|
|
2640
|
}; |
|
4
|
|
|
|
|
45167
|
|
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# customize Sub::Exporter to support "bang-syntax" for excluding symbols |
65
|
|
|
|
|
|
|
# see https://rt.cpan.org/Public/Bug/Display.html?id=80234 |
66
|
|
|
|
|
|
|
{ my @dont_export; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# detect symbols prefixed by '!' and remember them in @dont_export |
69
|
|
|
|
|
|
|
sub _sub_exporter_init { |
70
|
4
|
|
|
4
|
|
416
|
my ($collection, $context) = @_; |
71
|
4
|
|
|
|
|
8
|
my $args = $context->{import_args}; |
72
|
|
|
|
|
|
|
my ($exclude, $regular_args) |
73
|
4
|
100
|
66
|
5
|
|
64
|
= part {!ref $_->[0] && $_->[0] =~ /^!/ ? 0 : 1} @$args; |
|
5
|
|
|
|
|
55
|
|
74
|
4
|
|
|
|
|
20
|
@$args = @$regular_args; |
75
|
4
|
|
|
|
|
11
|
@dont_export = map {substr($_->[0], 1)} @$exclude; |
|
1
|
|
|
|
|
5
|
|
76
|
4
|
|
|
|
|
16
|
1; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# install symbols, except those that belong to @dont_export |
80
|
|
|
|
|
|
|
sub _sub_exporter_installer { |
81
|
4
|
|
|
4
|
|
35
|
my ($arg, $to_export) = @_; |
82
|
4
|
|
|
|
|
58
|
my %export_hash = @$to_export; |
83
|
4
|
|
|
|
|
20
|
delete @export_hash{@dont_export}; |
84
|
4
|
|
|
|
|
50
|
Sub::Exporter::default_installer($arg, [%export_hash]); |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# constructors group : for each domain constructor, we export a closure |
89
|
|
|
|
|
|
|
# that just calls new() on the corresponding subclass. For example, |
90
|
|
|
|
|
|
|
# Num(@args) is just equivalent to Data::Domain::Num->new(@args). |
91
|
|
|
|
|
|
|
sub _wrap_domain { |
92
|
56
|
|
|
56
|
|
2644
|
my ($class, $name, $args, $coll) = @_; |
93
|
56
|
|
|
131
|
|
207
|
return sub {return "Data::Domain::$name"->new(@_)}; |
|
131
|
|
|
|
|
44431
|
|
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# # shortcuts group : calling 'Whatever' with various pre-built options |
98
|
|
|
|
|
|
|
sub _wrap_shortcut_options { |
99
|
44
|
|
|
44
|
|
1448
|
my ($class, $name, $args, $coll) = @_; |
100
|
44
|
|
|
14
|
|
145
|
return sub {return Data::Domain::Whatever->new(@{$SHORTCUTS{$name}}, @_)}; |
|
14
|
|
|
|
|
4748
|
|
|
14
|
|
|
|
|
77
|
|
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
106
|
|
|
|
|
|
|
# messages |
107
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
my $builtin_msgs = { |
110
|
|
|
|
|
|
|
english => { |
111
|
|
|
|
|
|
|
Generic => { |
112
|
|
|
|
|
|
|
UNDEFINED => "undefined data", |
113
|
|
|
|
|
|
|
INVALID => "invalid", |
114
|
|
|
|
|
|
|
TOO_SMALL => "smaller than minimum '%s'", |
115
|
|
|
|
|
|
|
TOO_BIG => "bigger than maximum '%s'", |
116
|
|
|
|
|
|
|
EXCLUSION_SET => "belongs to exclusion set", |
117
|
|
|
|
|
|
|
MATCH_TRUE => "data true/false", |
118
|
|
|
|
|
|
|
MATCH_ISA => "is not a '%s'", |
119
|
|
|
|
|
|
|
MATCH_CAN => "does not have method '%s'", |
120
|
|
|
|
|
|
|
MATCH_DOES => "does not do '%s'", |
121
|
|
|
|
|
|
|
MATCH_BLESSED => "data blessed/unblessed", |
122
|
|
|
|
|
|
|
MATCH_PACKAGE => "data is/is not a package", |
123
|
|
|
|
|
|
|
MATCH_REF => "is/is not a reference", |
124
|
|
|
|
|
|
|
MATCH_SMART => "does not smart-match '%s'", |
125
|
|
|
|
|
|
|
MATCH_ISWEAK => "weak/strong reference", |
126
|
|
|
|
|
|
|
MATCH_READONLY=> "readonly data", |
127
|
|
|
|
|
|
|
MATCH_TAINTED => "tainted/untainted", |
128
|
|
|
|
|
|
|
}, |
129
|
|
|
|
|
|
|
Whatever => { |
130
|
|
|
|
|
|
|
MATCH_DEFINED => "data defined/undefined", |
131
|
|
|
|
|
|
|
}, |
132
|
|
|
|
|
|
|
Num => {INVALID => "invalid number",}, |
133
|
|
|
|
|
|
|
Date => {INVALID => "invalid date",}, |
134
|
|
|
|
|
|
|
String => { |
135
|
|
|
|
|
|
|
TOO_SHORT => "less than %d characters", |
136
|
|
|
|
|
|
|
TOO_LONG => "more than %d characters", |
137
|
|
|
|
|
|
|
SHOULD_MATCH => "should match '%s'", |
138
|
|
|
|
|
|
|
SHOULD_NOT_MATCH => "should not match '%s'", |
139
|
|
|
|
|
|
|
}, |
140
|
|
|
|
|
|
|
Handle => {INVALID => "is not an open filehandle"}, |
141
|
|
|
|
|
|
|
Enum => {NOT_IN_LIST => "not in enumeration list",}, |
142
|
|
|
|
|
|
|
List => { |
143
|
|
|
|
|
|
|
NOT_A_LIST => "is not an arrayref", |
144
|
|
|
|
|
|
|
TOO_SHORT => "less than %d items", |
145
|
|
|
|
|
|
|
TOO_LONG => "more than %d items", |
146
|
|
|
|
|
|
|
ANY => "should have at least one '%s'", |
147
|
|
|
|
|
|
|
}, |
148
|
|
|
|
|
|
|
Struct => { |
149
|
|
|
|
|
|
|
NOT_A_HASH => "is not a hashref", |
150
|
|
|
|
|
|
|
FORBIDDEN_FIELD => "contains forbidden field: '%s'" |
151
|
|
|
|
|
|
|
}, |
152
|
|
|
|
|
|
|
}, |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
"français" => { |
155
|
|
|
|
|
|
|
Generic => { |
156
|
|
|
|
|
|
|
UNDEFINED => "donnée non définie", |
157
|
|
|
|
|
|
|
INVALID => "incorrect", |
158
|
|
|
|
|
|
|
TOO_SMALL => "plus petit que le minimum '%s'", |
159
|
|
|
|
|
|
|
TOO_BIG => "plus grand que le maximum '%s'", |
160
|
|
|
|
|
|
|
EXCLUSION_SET => "fait partie des valeurs interdites", |
161
|
|
|
|
|
|
|
MATCH_TRUE => "donnée vraie/fausse", |
162
|
|
|
|
|
|
|
MATCH_ISA => "n'est pas un '%s'", |
163
|
|
|
|
|
|
|
MATCH_CAN => "n'a pas la méthode '%s'", |
164
|
|
|
|
|
|
|
MATCH_DOES => "ne se comporte pas comme un '%s'", |
165
|
|
|
|
|
|
|
MATCH_BLESSED => "donnée blessed/unblessed", |
166
|
|
|
|
|
|
|
MATCH_PACKAGE => "est/n'est pas un package", |
167
|
|
|
|
|
|
|
MATCH_REF => "est/n'est pas une référence", |
168
|
|
|
|
|
|
|
MATCH_SMART => "n'obéit pas au smart-match '%s'", |
169
|
|
|
|
|
|
|
MATCH_ISWEAK => "référence weak/strong", |
170
|
|
|
|
|
|
|
MATCH_READONLY=> "donnée readonly", |
171
|
|
|
|
|
|
|
MATCH_TAINTED => "tainted/untainted", |
172
|
|
|
|
|
|
|
}, |
173
|
|
|
|
|
|
|
Whatever => { |
174
|
|
|
|
|
|
|
MATCH_DEFINED => "donnée définie/non définie", |
175
|
|
|
|
|
|
|
}, |
176
|
|
|
|
|
|
|
Num => {INVALID => "nombre incorrect",}, |
177
|
|
|
|
|
|
|
Date => {INVALID => "date incorrecte",}, |
178
|
|
|
|
|
|
|
String => { |
179
|
|
|
|
|
|
|
TOO_SHORT => "moins de %d caractères", |
180
|
|
|
|
|
|
|
TOO_LONG => "plus de %d caractères", |
181
|
|
|
|
|
|
|
SHOULD_MATCH => "devrait être reconnu par la regex '%s'", |
182
|
|
|
|
|
|
|
SHOULD_NOT_MATCH => "ne devrait pas être reconnu par la regex '%s'", |
183
|
|
|
|
|
|
|
}, |
184
|
|
|
|
|
|
|
Handle => {INVALID => "n'est pas une filehandle ouverte"}, |
185
|
|
|
|
|
|
|
Enum => {NOT_IN_LIST => "n'appartient pas à la liste énumérée",}, |
186
|
|
|
|
|
|
|
List => { |
187
|
|
|
|
|
|
|
NOT_A_LIST => "n'est pas une arrayref", |
188
|
|
|
|
|
|
|
TOO_SHORT => "moins de %d éléments", |
189
|
|
|
|
|
|
|
TOO_LONG => "plus de %d éléments", |
190
|
|
|
|
|
|
|
ANY => "doit avoir au moins un '%s'", |
191
|
|
|
|
|
|
|
}, |
192
|
|
|
|
|
|
|
Struct => { |
193
|
|
|
|
|
|
|
NOT_A_HASH => "n'est pas une hashref", |
194
|
|
|
|
|
|
|
FORBIDDEN_FIELD => "contient le champ interdit: '%s'", |
195
|
|
|
|
|
|
|
}, |
196
|
|
|
|
|
|
|
}, |
197
|
|
|
|
|
|
|
}; |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
# inherit Int and Nat messages from Num messages |
200
|
|
|
|
|
|
|
foreach my $language (keys %$builtin_msgs) { |
201
|
|
|
|
|
|
|
$builtin_msgs->{$language}{$_} = $builtin_msgs->{$language}{Num} |
202
|
|
|
|
|
|
|
for qw/Int Nat/; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# default messages : english |
206
|
|
|
|
|
|
|
my $global_msgs = $builtin_msgs->{english}; |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
209
|
|
|
|
|
|
|
# PUBLIC METHODS |
210
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub messages { # private class method |
213
|
3
|
|
|
3
|
1
|
5045
|
my ($class, $new_messages) = @_; |
214
|
3
|
50
|
33
|
|
|
17
|
croak "messages() is a class method in Data::Domain" |
215
|
|
|
|
|
|
|
if ref $class or $class ne 'Data::Domain'; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
$global_msgs = (ref $new_messages) ? $new_messages |
218
|
3
|
100
|
|
|
|
20
|
: $builtin_msgs->{$new_messages} |
|
|
50
|
|
|
|
|
|
219
|
|
|
|
|
|
|
or croak "no such builtin messages ($new_messages)"; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub inspect { |
224
|
982
|
|
|
982
|
1
|
3501
|
my ($self, $data, $context) = @_; |
225
|
4
|
|
|
4
|
|
7865
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
5649
|
|
226
|
|
|
|
|
|
|
|
227
|
982
|
100
|
|
|
|
1814
|
if (!defined $data) { |
228
|
|
|
|
|
|
|
# success if data was optional; |
229
|
33
|
100
|
|
|
|
81
|
return if $self->{-optional}; |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
# only the 'Whatever' domain can accept undef; other domains will fail |
232
|
26
|
100
|
|
|
|
154
|
return $self->msg(UNDEFINED => '') |
233
|
|
|
|
|
|
|
unless $self->isa("Data::Domain::Whatever"); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
else { # if $data is defined |
236
|
|
|
|
|
|
|
# check some general properties |
237
|
949
|
100
|
|
|
|
2092
|
if (my $isa = $self->{-isa}) { |
238
|
2
|
|
|
2
|
|
73
|
try {$data->isa($isa)} |
239
|
2
|
100
|
|
|
|
14
|
or return $self->msg(MATCH_ISA => $isa); |
240
|
|
|
|
|
|
|
} |
241
|
948
|
100
|
|
|
|
1840
|
if (my $role = $self->{-does}) { |
242
|
4
|
100
|
|
|
|
13
|
does($data, $role) |
243
|
|
|
|
|
|
|
or return $self->msg(MATCH_DOES => $role); |
244
|
|
|
|
|
|
|
} |
245
|
946
|
100
|
|
|
|
2419
|
if (my $can = $self->{-can}) { |
246
|
3
|
100
|
|
|
|
20
|
$can = [$can] unless does($can, 'ARRAY'); |
247
|
3
|
|
|
|
|
862
|
foreach my $method (@$can) { |
248
|
5
|
|
|
5
|
|
131
|
try {$data->can($method)} |
249
|
5
|
100
|
|
|
|
41
|
or return $self->msg(MATCH_CAN => $method); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
} |
252
|
945
|
100
|
|
|
|
1721
|
if (my $match_target = $self->{-matches}) { |
253
|
2
|
100
|
|
|
|
20
|
match::simple::match($data, $match_target) |
254
|
|
|
|
|
|
|
or return $self->msg(MATCH_SMART => $match_target); |
255
|
|
|
|
|
|
|
} |
256
|
944
|
100
|
|
|
|
1564
|
if ($self->{-has}) { |
257
|
|
|
|
|
|
|
# EXPERIMENTAL: check methods results |
258
|
1
|
|
|
|
|
6
|
my @msgs = $self->_check_has($data, $context); |
259
|
1
|
50
|
|
|
|
17
|
return {HAS => \@msgs} if @msgs; |
260
|
|
|
|
|
|
|
} |
261
|
943
|
100
|
|
|
|
1573
|
if (defined $self->{-blessed}) { |
262
|
|
|
|
|
|
|
return $self->msg(MATCH_BLESSED => $self->{-blessed}) |
263
|
8
|
100
|
100
|
|
|
65
|
if Scalar::Util::blessed($data) xor $self->{-blessed}; |
264
|
|
|
|
|
|
|
} |
265
|
939
|
100
|
|
|
|
1529
|
if (defined $self->{-package}) { |
266
|
|
|
|
|
|
|
return $self->msg(MATCH_PACKAGE => $self->{-package}) |
267
|
3
|
100
|
100
|
|
|
34
|
if (!ref($data) && $data->isa($data)) xor $self->{-package}; |
|
|
|
50
|
|
|
|
|
268
|
|
|
|
|
|
|
} |
269
|
937
|
50
|
|
|
|
1530
|
if (defined $self->{-isweak}) { |
270
|
|
|
|
|
|
|
return $self->msg(MATCH_ISWEAK => $self->{-isweak}) |
271
|
0
|
0
|
0
|
|
|
0
|
if Scalar::Util::isweak($data) xor $self->{-isweak}; |
272
|
|
|
|
|
|
|
} |
273
|
937
|
50
|
|
|
|
1479
|
if (defined $self->{-readonly}) { |
274
|
|
|
|
|
|
|
return $self->msg(MATCH_READONLY => $self->{-readonly}) |
275
|
0
|
0
|
0
|
|
|
0
|
if Scalar::Util::readonly($data) xor $self->{-readonly}; |
276
|
|
|
|
|
|
|
} |
277
|
937
|
50
|
|
|
|
1689
|
if (defined $self->{-tainted}) { |
278
|
|
|
|
|
|
|
return $self->msg(MATCH_TAINTED => $self->{-tainted}) |
279
|
0
|
0
|
0
|
|
|
0
|
if Scalar::Util::readonly($data) xor $self->{-tainted}; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# properties that must be checked against both defined and undef data |
284
|
946
|
100
|
|
|
|
1592
|
if (defined $self->{-true}) { |
285
|
|
|
|
|
|
|
return $self->msg(MATCH_TRUE => $self->{-true}) |
286
|
11
|
100
|
100
|
|
|
55
|
if $data xor $self->{-true}; |
287
|
|
|
|
|
|
|
} |
288
|
941
|
100
|
|
|
|
1516
|
if (defined $self->{-ref}) { |
289
|
|
|
|
|
|
|
return $self->msg(MATCH_REF => $self->{-ref}) |
290
|
6
|
100
|
100
|
|
|
29
|
if ref $data xor $self->{-ref}; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# now call domain-specific _inspect() |
294
|
938
|
|
|
|
|
2647
|
return $self->_inspect($data, $context) |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub _check_has { |
299
|
1
|
|
|
1
|
|
2
|
my ($self, $data, $context) = @_; |
300
|
|
|
|
|
|
|
|
301
|
1
|
|
|
|
|
1
|
my @msgs; |
302
|
1
|
|
|
|
|
2
|
my $iterator = natatime 2, @{$self->{-has}}; |
|
1
|
|
|
|
|
8
|
|
303
|
1
|
|
|
|
|
12
|
while (my ($meth_to_call, $expectation) = $iterator->()) { |
304
|
3
|
100
|
|
|
|
13
|
my ($meth, @args) = does($meth_to_call, 'ARRAY') ? @$meth_to_call |
305
|
|
|
|
|
|
|
: ($meth_to_call); |
306
|
3
|
|
|
|
|
851
|
my $msg; |
307
|
3
|
50
|
|
|
|
11
|
if (does($expectation, 'ARRAY')) { |
308
|
0
|
|
|
0
|
|
0
|
$msg = try {my @result = $data->$meth(@args); |
309
|
0
|
|
|
|
|
0
|
my $domain = List(@$expectation); |
310
|
0
|
|
|
|
|
0
|
$domain->inspect(\@result)} |
311
|
0
|
|
|
0
|
|
0
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
else { |
314
|
3
|
|
|
3
|
|
160
|
$msg = try {my $result = $data->$meth(@args); |
315
|
2
|
|
|
|
|
50
|
$expectation->inspect($result)} |
316
|
3
|
|
|
1
|
|
253
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
1
|
|
|
|
|
24
|
|
|
1
|
|
|
|
|
5
|
|
317
|
|
|
|
|
|
|
} |
318
|
3
|
100
|
|
|
|
55
|
push @msgs, $meth_to_call => $msg if $msg; |
319
|
|
|
|
|
|
|
} |
320
|
1
|
|
|
|
|
8
|
return @msgs; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
sub _check_returns { |
326
|
0
|
|
|
0
|
|
0
|
my ($self, $data, $context) = @_; |
327
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
0
|
my @msgs; |
329
|
0
|
|
|
|
|
0
|
my $iterator = natatime 2, @{$self->{-returns}}; |
|
0
|
|
|
|
|
0
|
|
330
|
0
|
|
|
|
|
0
|
while (my ($args, $expectation) = $iterator->()) { |
331
|
0
|
|
|
|
|
0
|
my $msg; |
332
|
0
|
0
|
|
|
|
0
|
if (does($expectation, 'ARRAY')) { |
333
|
0
|
|
|
0
|
|
0
|
$msg = try {my @result = $data->(@$args); |
334
|
0
|
|
|
|
|
0
|
my $domain = List(@$expectation); |
335
|
0
|
|
|
|
|
0
|
$domain->inspect(\@result)} |
336
|
0
|
|
|
0
|
|
0
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
else { |
339
|
0
|
|
|
0
|
|
0
|
$msg = try {my $result = $data->(@$args); |
340
|
0
|
|
|
|
|
0
|
$expectation->inspect($result)} |
341
|
0
|
|
|
0
|
|
0
|
catch {(my $error_msg = $_) =~ s/\bat\b.*//s; $error_msg}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
342
|
|
|
|
|
|
|
} |
343
|
0
|
0
|
|
|
|
0
|
push @msgs, $args => $msg if $msg; |
344
|
|
|
|
|
|
|
} |
345
|
0
|
|
|
|
|
0
|
return @msgs; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
352
|
|
|
|
|
|
|
# METHODS FOR INTERNAL USE |
353
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub msg { |
357
|
240
|
|
|
240
|
1
|
2040
|
my ($self, $msg_id, @args) = @_; |
358
|
240
|
|
|
|
|
440
|
my $msgs = $self->{-messages}; |
359
|
240
|
|
|
|
|
476
|
my $subclass = $self->subclass; |
360
|
240
|
|
66
|
|
|
798
|
my $name = $self->{-name} || $subclass; |
361
|
240
|
|
|
|
|
291
|
my $msg; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# perl v5.22 and above warns if there are too many @args for sprintf. |
364
|
|
|
|
|
|
|
# The line below prevents that warning |
365
|
4
|
|
|
4
|
|
31
|
no if $] ge '5.022000', warnings => 'redundant'; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
47
|
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
# if there is a user_defined message, return it |
368
|
240
|
100
|
|
|
|
448
|
if (defined $msgs) { |
369
|
11
|
|
|
|
|
28
|
for (ref $msgs) { |
370
|
11
|
100
|
|
|
|
37
|
/^CODE/ and return $msgs->($msg_id, @args); # user function |
371
|
10
|
100
|
|
|
|
82
|
/^$/ and return "$name: $msgs"; # user constant string |
372
|
2
|
50
|
|
|
|
10
|
/^HASH/ and do { $msg = $msgs->{$msg_id} # user hash of msgs |
|
2
|
50
|
|
|
|
29
|
|
373
|
|
|
|
|
|
|
and return sprintf "$name: $msg", @args; |
374
|
0
|
|
|
|
|
0
|
last; # not found in this hash - revert to $global_msgs |
375
|
|
|
|
|
|
|
}; |
376
|
0
|
|
|
|
|
0
|
croak "invalid -messages option"; # otherwise |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# otherwise, try global messages |
381
|
229
|
100
|
|
|
|
476
|
return $global_msgs->($msg_id, @args) if ref $global_msgs eq 'CODE'; |
382
|
|
|
|
|
|
|
$msg = $global_msgs->{$subclass}{$msg_id} # otherwise |
383
|
228
|
50
|
66
|
|
|
911
|
|| $global_msgs->{Generic}{$msg_id} |
384
|
|
|
|
|
|
|
or croak "no error string for message $msg_id"; |
385
|
|
|
|
|
|
|
|
386
|
228
|
|
|
|
|
1396
|
return sprintf "$name: $msg", @args; |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub subclass { # returns the class name without initial 'Data::Domain::' |
391
|
367
|
|
|
367
|
1
|
561
|
my ($self) = @_; |
392
|
367
|
|
33
|
|
|
779
|
my $class = ref($self) || $self; |
393
|
367
|
|
|
|
|
1480
|
(my $subclass = $class) =~ s/^Data::Domain:://; |
394
|
367
|
|
|
|
|
1849
|
return $subclass; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
sub _expand_range { |
399
|
127
|
|
|
127
|
|
247
|
my ($self, $range_field, $min_field, $max_field) = @_; |
400
|
127
|
|
66
|
|
|
784
|
my $name = $self->{-name} || $self->subclass; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# the range field will be replaced by min and max fields |
403
|
127
|
100
|
|
|
|
385
|
if (my $range = delete $self->{$range_field}) { |
404
|
13
|
|
|
|
|
32
|
for ($min_field, $max_field) { |
405
|
26
|
50
|
|
|
|
59
|
not defined $self->{$_} |
406
|
|
|
|
|
|
|
or croak "$name: incompatible options: $range_field / $_"; |
407
|
|
|
|
|
|
|
} |
408
|
13
|
50
|
33
|
|
|
42
|
does($range, 'ARRAY') and @$range == 2 |
409
|
|
|
|
|
|
|
or croak "$name: invalid argument for $range"; |
410
|
13
|
|
|
|
|
314
|
@{$self}{$min_field, $max_field} = @$range; |
|
13
|
|
|
|
|
40
|
|
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub _check_min_max { |
416
|
126
|
|
|
126
|
|
245
|
my ($self, $min_field, $max_field, $cmp_func) = @_; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# choose the appropriate comparison function |
419
|
126
|
100
|
|
13
|
|
302
|
if ($cmp_func eq '<=') {$cmp_func = sub {$_[0] <= $_[1]}} |
|
88
|
100
|
|
|
|
258
|
|
|
13
|
50
|
|
|
|
49
|
|
420
|
25
|
|
|
4
|
|
85
|
elsif ($cmp_func eq 'le') {$cmp_func = sub {$_[0] le $_[1]}} |
|
4
|
|
|
|
|
16
|
|
421
|
|
|
|
|
|
|
elsif (does($cmp_func, 'CODE')) {} # already a coderef, do nothing |
422
|
0
|
|
|
|
|
0
|
else {croak "inappropriate cmp_func for _check_min_max"} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
# check that min is smaller than max |
425
|
126
|
|
|
|
|
2118
|
my ($min, $max) = @{$self}{$min_field, $max_field}; |
|
126
|
|
|
|
|
272
|
|
426
|
126
|
100
|
100
|
|
|
457
|
if (defined $min && defined $max) { |
427
|
21
|
100
|
|
|
|
49
|
$cmp_func->($min, $max) |
428
|
|
|
|
|
|
|
or croak $self->subclass . ": incompatible min/max values ($min/$max)"; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub _build_subdomain { |
434
|
469
|
|
|
469
|
|
901
|
my ($self, $domain, $context) = @_; |
435
|
4
|
|
|
4
|
|
3188
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
2970
|
|
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# avoid infinite loop |
438
|
469
|
100
|
|
|
|
587
|
@{$context->{path}} < $MAX_DEEP |
|
469
|
|
|
|
|
3888
|
|
439
|
|
|
|
|
|
|
or croak "inspect() deepness exceeded $MAX_DEEP; " |
440
|
|
|
|
|
|
|
. "modify \$Data::Domain::MAX_DEEP if you need more"; |
441
|
|
|
|
|
|
|
|
442
|
468
|
100
|
|
|
|
1070
|
if (does($domain, 'Data::Domain')) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# already a domain, nothing to do |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
elsif (does($domain, 'CODE')) { |
446
|
|
|
|
|
|
|
# this is a lazy domain, need to call the coderef to get a real domain |
447
|
230
|
|
|
230
|
|
9198
|
$domain = try {$domain->($context)} |
448
|
|
|
|
|
|
|
catch { # remove "at source_file, line ..." from error message |
449
|
1
|
|
|
1
|
|
263
|
(my $error_msg = $_) =~ s/\bat\b.*//s; |
450
|
|
|
|
|
|
|
# return an empty domain that reports the error message |
451
|
1
|
|
|
|
|
6
|
Data::Domain::Empty->new(-name => "domain parameters", |
452
|
|
|
|
|
|
|
-messages => $error_msg); |
453
|
230
|
|
|
|
|
8254
|
}; |
454
|
|
|
|
|
|
|
# did we really get a domain ? |
455
|
230
|
50
|
|
|
|
3224
|
does($domain, "Data::Domain") |
456
|
|
|
|
|
|
|
or croak "lazy domain coderef returned an invalid domain"; |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
elsif (!ref $domain) { |
459
|
|
|
|
|
|
|
# this is a scalar, build a constant domain with that single value |
460
|
6
|
100
|
|
|
|
274
|
my $subclass = Scalar::Util::looks_like_number($domain) ? 'Num' : 'String'; |
461
|
6
|
|
|
|
|
28
|
$domain = "Data::Domain::$subclass"->new(-min => $domain, |
462
|
|
|
|
|
|
|
-max => $domain, |
463
|
|
|
|
|
|
|
-name => "constant $subclass"); |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
else { |
466
|
0
|
|
|
|
|
0
|
croak "unknown subdomain : $domain"; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
468
|
|
|
|
|
8086
|
return $domain; |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
474
|
|
|
|
|
|
|
# UTILITY FUNCTIONS (NOT METHODS) |
475
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# valid options for all subclasses |
478
|
|
|
|
|
|
|
my @common_options = qw/-optional -name -messages |
479
|
|
|
|
|
|
|
-true -isa -can -does -matches -ref |
480
|
|
|
|
|
|
|
-has -returns |
481
|
|
|
|
|
|
|
-blessed -package -isweak -readonly -tainted/; |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
sub _parse_args { |
484
|
153
|
|
|
153
|
|
316
|
my ($args_ref, $options_ref, $default_option, $arg_type) = @_; |
485
|
|
|
|
|
|
|
|
486
|
153
|
|
|
|
|
214
|
my %parsed; |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# parse named arguments |
489
|
153
|
|
100
|
|
|
830
|
while (@$args_ref and $args_ref->[0] =~ /^-/) { |
490
|
123
|
50
|
|
518
|
|
608
|
any {$args_ref->[0] eq $_} (@$options_ref, @common_options) |
|
518
|
|
|
|
|
804
|
|
491
|
|
|
|
|
|
|
or croak "invalid argument: $args_ref->[0]"; |
492
|
123
|
|
|
|
|
407
|
my ($key, $val) = (shift @$args_ref, shift @$args_ref); |
493
|
123
|
|
|
|
|
469
|
$parsed{$key} = $val; |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# remaining arguments are mapped to the default option |
497
|
153
|
100
|
|
|
|
465
|
if (@$args_ref) { |
498
|
24
|
50
|
|
|
|
46
|
$default_option or croak "too many args to new()"; |
499
|
24
|
50
|
|
|
|
51
|
not exists $parsed{$default_option} |
500
|
|
|
|
|
|
|
or croak "can't have default args if $default_option is set"; |
501
|
24
|
50
|
|
|
|
80
|
$parsed{$default_option} |
|
|
100
|
|
|
|
|
|
502
|
|
|
|
|
|
|
= $arg_type eq 'scalar' ? $args_ref->[0] |
503
|
|
|
|
|
|
|
: $arg_type eq 'arrayref' ? $args_ref |
504
|
|
|
|
|
|
|
: croak "unknown type for default option: $arg_type"; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
153
|
|
|
|
|
342
|
return \%parsed; |
508
|
|
|
|
|
|
|
} |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
512
|
|
|
|
|
|
|
# implementation for overloaded operators |
513
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
514
|
|
|
|
|
|
|
sub _matches { |
515
|
2
|
|
|
2
|
|
1146
|
my ($self, $data, $call_order) = @_; |
516
|
2
|
|
|
|
|
6
|
$Data::Domain::MESSAGE = $self->inspect($data); |
517
|
2
|
|
|
|
|
11
|
return !$Data::Domain::MESSAGE; # smart match successful if no error message |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
sub _stringify { |
521
|
213
|
|
|
213
|
|
2797
|
my ($self) = @_; |
522
|
213
|
|
|
|
|
589
|
my $dumper = Data::Dumper->new([$self])->Indent(0)->Terse(1); |
523
|
213
|
|
|
|
|
7112
|
return $dumper->Dump; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
#====================================================================== |
529
|
|
|
|
|
|
|
package Data::Domain::Whatever; |
530
|
|
|
|
|
|
|
#====================================================================== |
531
|
4
|
|
|
4
|
|
31
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
112
|
|
532
|
4
|
|
|
4
|
|
21
|
use warnings; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
98
|
|
533
|
4
|
|
|
4
|
|
86
|
use Carp; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
249
|
|
534
|
4
|
|
|
4
|
|
34
|
use Scalar::Does qw/does/; |
|
4
|
|
|
|
|
53
|
|
|
4
|
|
|
|
|
36
|
|
535
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
sub new { |
538
|
26
|
|
|
26
|
|
50
|
my $class = shift; |
539
|
26
|
|
|
|
|
54
|
my @options = qw/-defined/; |
540
|
26
|
|
|
|
|
64
|
my $self = Data::Domain::_parse_args( \@_, \@options ); |
541
|
26
|
|
|
|
|
56
|
bless $self, $class; |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
not ($self->{-defined } && $self->{-optional}) |
544
|
26
|
50
|
66
|
|
|
151
|
or croak "both -defined and -optional: meaningless!"; |
545
|
|
|
|
|
|
|
|
546
|
26
|
|
|
|
|
115
|
return $self; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
sub _inspect { |
550
|
32
|
|
|
32
|
|
62
|
my ($self, $data) = @_; |
551
|
|
|
|
|
|
|
|
552
|
32
|
100
|
|
|
|
63
|
if (defined $self->{-defined}) { |
553
|
|
|
|
|
|
|
return $self->msg(MATCH_DEFINED => $self->{-defined}) |
554
|
9
|
100
|
100
|
|
|
90
|
if defined($data) xor $self->{-defined}; |
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
# otherwise, success |
558
|
27
|
|
|
|
|
133
|
return; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
#====================================================================== |
563
|
|
|
|
|
|
|
package Data::Domain::Empty; |
564
|
|
|
|
|
|
|
#====================================================================== |
565
|
4
|
|
|
4
|
|
2950
|
use strict; |
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
74
|
|
566
|
4
|
|
|
4
|
|
19
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
102
|
|
567
|
4
|
|
|
4
|
|
23
|
use Carp; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
548
|
|
568
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
sub new { |
571
|
2
|
|
|
2
|
|
8
|
my $class = shift; |
572
|
2
|
|
|
|
|
5
|
my @options = (); |
573
|
2
|
|
|
|
|
7
|
my $self = Data::Domain::_parse_args( \@_, \@options ); |
574
|
2
|
|
|
|
|
30
|
bless $self, $class; |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
sub _inspect { |
578
|
5
|
|
|
5
|
|
30
|
my ($self, $data) = @_; |
579
|
|
|
|
|
|
|
|
580
|
5
|
|
|
|
|
16
|
return $self->msg(INVALID => ''); # always fails |
581
|
|
|
|
|
|
|
} |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
#====================================================================== |
585
|
|
|
|
|
|
|
package Data::Domain::Num; |
586
|
|
|
|
|
|
|
#====================================================================== |
587
|
4
|
|
|
4
|
|
27
|
use strict; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
79
|
|
588
|
4
|
|
|
4
|
|
18
|
use warnings; |
|
4
|
|
|
|
|
15
|
|
|
4
|
|
|
|
|
85
|
|
589
|
4
|
|
|
4
|
|
19
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
177
|
|
590
|
4
|
|
|
4
|
|
32
|
use Scalar::Util qw/looks_like_number/; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
149
|
|
591
|
4
|
|
|
4
|
|
22
|
use Try::Tiny; |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
1515
|
|
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
sub new { |
596
|
50
|
|
|
50
|
|
95
|
my $class = shift; |
597
|
50
|
|
|
|
|
110
|
my @options = qw/-range -min -max -not_in/; |
598
|
50
|
|
|
|
|
131
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
599
|
50
|
|
|
|
|
101
|
bless $self, $class; |
600
|
|
|
|
|
|
|
|
601
|
50
|
|
|
|
|
147
|
$self->_expand_range(qw/-range -min -max/); |
602
|
50
|
|
|
|
|
142
|
$self->_check_min_max(qw/-min -max <=/); |
603
|
|
|
|
|
|
|
|
604
|
49
|
100
|
|
|
|
112
|
if ($self->{-not_in}) { |
605
|
1
|
|
|
1
|
|
35
|
try {my $vals = $self->{-not_in}; |
606
|
1
|
50
|
|
|
|
10
|
@$vals > 0 and not grep {!looks_like_number($_)} @$vals} |
|
2
|
|
|
|
|
13
|
|
607
|
1
|
50
|
|
|
|
16
|
or croak "-not_in : needs an arrayref of numbers"; |
608
|
|
|
|
|
|
|
} |
609
|
|
|
|
|
|
|
|
610
|
49
|
|
|
|
|
307
|
return $self; |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub _inspect { |
614
|
294
|
|
|
294
|
|
501
|
my ($self, $data) = @_; |
615
|
|
|
|
|
|
|
|
616
|
294
|
100
|
|
|
|
866
|
looks_like_number($data) |
617
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
618
|
|
|
|
|
|
|
|
619
|
187
|
100
|
|
|
|
433
|
if (defined $self->{-min}) { |
620
|
|
|
|
|
|
|
$data >= $self->{-min} |
621
|
31
|
100
|
|
|
|
92
|
or return $self->msg(TOO_SMALL => $self->{-min}); |
622
|
|
|
|
|
|
|
} |
623
|
181
|
100
|
|
|
|
328
|
if (defined $self->{-max}) { |
624
|
|
|
|
|
|
|
$data <= $self->{-max} |
625
|
12
|
100
|
|
|
|
37
|
or return $self->msg(TOO_BIG => $self->{-max}); |
626
|
|
|
|
|
|
|
} |
627
|
177
|
100
|
|
|
|
300
|
if (defined $self->{-not_in}) { |
628
|
5
|
100
|
|
|
|
8
|
grep {$data == $_} @{$self->{-not_in}} |
|
10
|
|
|
|
|
31
|
|
|
5
|
|
|
|
|
11
|
|
629
|
|
|
|
|
|
|
and return $self->msg(EXCLUSION_SET => $data); |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
175
|
|
|
|
|
603
|
return; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
#====================================================================== |
637
|
|
|
|
|
|
|
package Data::Domain::Int; |
638
|
|
|
|
|
|
|
#====================================================================== |
639
|
4
|
|
|
4
|
|
36
|
use strict; |
|
4
|
|
|
|
|
15
|
|
|
4
|
|
|
|
|
114
|
|
640
|
4
|
|
|
4
|
|
37
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
606
|
|
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
our @ISA = 'Data::Domain::Num'; |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
sub _inspect { |
645
|
76
|
|
|
76
|
|
148
|
my ($self, $data) = @_; |
646
|
|
|
|
|
|
|
|
647
|
76
|
100
|
66
|
|
|
540
|
defined($data) and $data =~ /^-?\d+$/ |
648
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
649
|
56
|
|
|
|
|
145
|
return $self->SUPER::_inspect($data); |
650
|
|
|
|
|
|
|
} |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
#====================================================================== |
654
|
|
|
|
|
|
|
package Data::Domain::Nat; |
655
|
|
|
|
|
|
|
#====================================================================== |
656
|
4
|
|
|
4
|
|
39
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
132
|
|
657
|
4
|
|
|
4
|
|
25
|
use warnings; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
559
|
|
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
our @ISA = 'Data::Domain::Num'; |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
sub _inspect { |
662
|
3
|
|
|
3
|
|
14
|
my ($self, $data) = @_; |
663
|
|
|
|
|
|
|
|
664
|
3
|
100
|
66
|
|
|
33
|
defined($data) and $data =~ /^\d+$/ |
665
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
666
|
2
|
|
|
|
|
9
|
return $self->SUPER::_inspect($data); |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
#====================================================================== |
671
|
|
|
|
|
|
|
package Data::Domain::String; |
672
|
|
|
|
|
|
|
#====================================================================== |
673
|
4
|
|
|
4
|
|
27
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
123
|
|
674
|
4
|
|
|
4
|
|
21
|
use warnings; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
116
|
|
675
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
2019
|
|
676
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
sub new { |
679
|
25
|
|
|
25
|
|
45
|
my $class = shift; |
680
|
25
|
|
|
|
|
66
|
my @options = qw/-regex -antiregex |
681
|
|
|
|
|
|
|
-range -min -max |
682
|
|
|
|
|
|
|
-length -min_length -max_length |
683
|
|
|
|
|
|
|
-not_in/; |
684
|
25
|
|
|
|
|
72
|
my $self = Data::Domain::_parse_args(\@_, \@options, -regex => 'scalar'); |
685
|
25
|
|
|
|
|
48
|
bless $self, $class; |
686
|
|
|
|
|
|
|
|
687
|
25
|
|
|
|
|
76
|
$self->_expand_range(qw/-range -min -max/); |
688
|
25
|
|
|
|
|
78
|
$self->_check_min_max(qw/-min -max le/); |
689
|
|
|
|
|
|
|
|
690
|
25
|
|
|
|
|
58
|
$self->_expand_range(qw/-length -min_length -max_length/); |
691
|
25
|
|
|
|
|
68
|
$self->_check_min_max(qw/-min_length -max_length <=/); |
692
|
|
|
|
|
|
|
|
693
|
24
|
|
|
|
|
138
|
return $self; |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
sub _inspect { |
697
|
162
|
|
|
162
|
|
276
|
my ($self, $data) = @_; |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
# $data must be Unref or obj with a stringification method |
700
|
162
|
100
|
100
|
|
|
379
|
!ref($data) || overload::Method($data, '""') |
701
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
702
|
159
|
100
|
|
|
|
381
|
if ($self->{-min_length}) { |
703
|
|
|
|
|
|
|
length($data) >= $self->{-min_length} |
704
|
6
|
100
|
|
|
|
32
|
or return $self->msg(TOO_SHORT => $self->{-min_length}); |
705
|
|
|
|
|
|
|
} |
706
|
158
|
100
|
|
|
|
321
|
if (defined $self->{-max_length}) { |
707
|
|
|
|
|
|
|
length($data) <= $self->{-max_length} |
708
|
5
|
100
|
|
|
|
17
|
or return $self->msg(TOO_LONG => $self->{-max_length}); |
709
|
|
|
|
|
|
|
} |
710
|
155
|
100
|
|
|
|
315
|
if ($self->{-regex}) { |
711
|
|
|
|
|
|
|
$data =~ $self->{-regex} |
712
|
132
|
100
|
|
|
|
801
|
or return $self->msg(SHOULD_MATCH => $self->{-regex}); |
713
|
|
|
|
|
|
|
} |
714
|
142
|
100
|
|
|
|
307
|
if ($self->{-antiregex}) { |
715
|
|
|
|
|
|
|
$data !~ $self->{-antiregex} |
716
|
2
|
100
|
|
|
|
18
|
or return $self->msg(SHOULD_NOT_MATCH => $self->{-antiregex}); |
717
|
|
|
|
|
|
|
} |
718
|
141
|
100
|
|
|
|
241
|
if (defined $self->{-min}) { |
719
|
|
|
|
|
|
|
$data ge $self->{-min} |
720
|
4
|
100
|
|
|
|
20
|
or return $self->msg(TOO_SMALL => $self->{-min}); |
721
|
|
|
|
|
|
|
} |
722
|
140
|
100
|
|
|
|
262
|
if (defined $self->{-max}) { |
723
|
|
|
|
|
|
|
$data le $self->{-max} |
724
|
3
|
100
|
|
|
|
21
|
or return $self->msg(TOO_BIG => $self->{-max}); |
725
|
|
|
|
|
|
|
} |
726
|
139
|
100
|
|
|
|
280
|
if ($self->{-not_in}) { |
727
|
1
|
50
|
|
|
|
2
|
grep {$data eq $_} @{$self->{-not_in}} |
|
2
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
6
|
|
728
|
|
|
|
|
|
|
and return $self->msg(EXCLUSION_SET => $data); |
729
|
|
|
|
|
|
|
} |
730
|
|
|
|
|
|
|
|
731
|
139
|
|
|
|
|
299
|
return; |
732
|
|
|
|
|
|
|
} |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
#====================================================================== |
736
|
|
|
|
|
|
|
package Data::Domain::Date; |
737
|
|
|
|
|
|
|
#====================================================================== |
738
|
4
|
|
|
4
|
|
29
|
use strict; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
131
|
|
739
|
4
|
|
|
4
|
|
21
|
use warnings; |
|
4
|
|
|
|
|
19
|
|
|
4
|
|
|
|
|
99
|
|
740
|
4
|
|
|
4
|
|
18
|
use Carp; |
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
222
|
|
741
|
4
|
|
|
4
|
|
24
|
use Try::Tiny; |
|
4
|
|
|
|
|
18
|
|
|
4
|
|
|
|
|
357
|
|
742
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
|
745
|
4
|
|
|
|
|
26
|
use autouse 'Date::Calc' => qw/Decode_Date_EU Decode_Date_US Date_to_Text |
746
|
4
|
|
|
4
|
|
1991
|
Delta_Days Add_Delta_Days Today check_date/; |
|
4
|
|
|
|
|
3067
|
|
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
my $date_parser = \&Decode_Date_EU; |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
751
|
|
|
|
|
|
|
# utility functions |
752
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
753
|
|
|
|
|
|
|
sub _print_date { |
754
|
3
|
|
|
3
|
|
7
|
my $date = shift; |
755
|
3
|
|
|
|
|
7
|
$date = _expand_dynamic_date($date); |
756
|
3
|
|
|
|
|
18
|
return Date_to_Text(@$date); |
757
|
|
|
|
|
|
|
} |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
my $dynamic_date = qr/^(today|yesterday|tomorrow)$/; |
761
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
sub _expand_dynamic_date { |
763
|
42
|
|
|
42
|
|
56
|
my $date = shift; |
764
|
42
|
100
|
|
|
|
78
|
if (not ref $date) { |
765
|
|
|
|
|
|
|
$date = { |
766
|
|
|
|
|
|
|
today => [Today], |
767
|
|
|
|
|
|
|
yesterday => [Add_Delta_Days(Today, -1)], |
768
|
|
|
|
|
|
|
tomorrow => [Add_Delta_Days(Today, +1)] |
769
|
7
|
50
|
|
|
|
332
|
}->{$date} or croak "unexpected date : $date"; |
770
|
|
|
|
|
|
|
} |
771
|
42
|
|
|
|
|
240
|
return $date; |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
sub _date_cmp { |
775
|
15
|
|
|
15
|
|
42
|
my ($d1, $d2) = map {_expand_dynamic_date($_)} @_; |
|
30
|
|
|
|
|
44
|
|
776
|
15
|
|
|
|
|
108
|
return -Delta_Days(@$d1, @$d2); |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
781
|
|
|
|
|
|
|
# public API |
782
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
sub parser { |
785
|
1
|
|
|
1
|
|
427
|
my ($class, $new_parser) = @_; |
786
|
1
|
50
|
|
|
|
5
|
not ref $class or croak "Data::Domain::Date::parser is a class method"; |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
$date_parser = |
789
|
|
|
|
|
|
|
(ref $new_parser eq 'CODE') |
790
|
|
|
|
|
|
|
? $new_parser |
791
|
|
|
|
|
|
|
: {US => \&Decode_Date_US, |
792
|
1
|
50
|
|
|
|
12
|
EU => \&Decode_Date_EU}->{$new_parser} |
|
|
50
|
|
|
|
|
|
793
|
|
|
|
|
|
|
or croak "unknown date parser : $new_parser"; |
794
|
1
|
|
|
|
|
3
|
return $date_parser; |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
sub new { |
799
|
11
|
|
|
11
|
|
2484
|
my $class = shift; |
800
|
11
|
|
|
|
|
27
|
my @options = qw/-range -min -max -not_in/; |
801
|
11
|
|
|
|
|
31
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
802
|
11
|
|
|
|
|
18
|
bless $self, $class; |
803
|
|
|
|
|
|
|
|
804
|
11
|
|
|
|
|
45
|
$self->_expand_range(qw/-range -min -max/); |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# parse date boundaries into internal representation (arrayrefs) |
807
|
11
|
|
|
|
|
25
|
for my $bound (qw/-min -max/) { |
808
|
21
|
100
|
100
|
|
|
112
|
if ($self->{$bound} and $self->{$bound} !~ $dynamic_date) { |
809
|
6
|
100
|
|
|
|
21
|
my @date = $date_parser->($self->{$bound}) |
810
|
|
|
|
|
|
|
or croak "invalid date ($bound): $self->{$bound}"; |
811
|
5
|
|
|
|
|
85
|
$self->{$bound} = \@date; |
812
|
|
|
|
|
|
|
} |
813
|
|
|
|
|
|
|
} |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
# check order of boundaries |
816
|
10
|
|
|
2
|
|
66
|
$self->_check_min_max(qw/-min -max/, sub {_date_cmp($_[0], $_[1]) <= 0}); |
|
2
|
|
|
|
|
9
|
|
817
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
# parse dates in the exclusion set into internal representation |
819
|
9
|
100
|
|
|
|
101
|
if ($self->{-not_in}) { |
820
|
1
|
|
|
|
|
2
|
my @excl_dates; |
821
|
|
|
|
|
|
|
try { |
822
|
1
|
|
|
1
|
|
26
|
foreach my $date (@{$self->{-not_in}}) { |
|
1
|
|
|
|
|
6
|
|
823
|
2
|
100
|
|
|
|
20
|
if ($date =~ $dynamic_date) { |
824
|
1
|
|
|
|
|
5
|
push @excl_dates, $date; |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
else { |
827
|
1
|
50
|
|
|
|
6
|
my @parsed_date = $date_parser->($date) or die "wrong date"; |
828
|
1
|
|
|
|
|
18
|
push @excl_dates, \@parsed_date; |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
} |
831
|
1
|
|
|
|
|
4
|
@excl_dates > 0; |
832
|
|
|
|
|
|
|
} |
833
|
1
|
50
|
|
|
|
10
|
or croak "-not_in : needs an arrayref of dates"; |
834
|
1
|
|
|
|
|
20
|
$self->{-not_in} = \@excl_dates; |
835
|
|
|
|
|
|
|
} |
836
|
|
|
|
|
|
|
|
837
|
9
|
|
|
|
|
54
|
return $self; |
838
|
|
|
|
|
|
|
} |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
sub _inspect { |
842
|
18
|
|
|
18
|
|
41
|
my ($self, $data) = @_; |
843
|
|
|
|
|
|
|
|
844
|
18
|
|
|
18
|
|
93
|
my @date = try {$date_parser->($data)}; |
|
18
|
|
|
|
|
468
|
|
845
|
18
|
100
|
66
|
|
|
14183
|
@date && check_date(@date) |
846
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
847
|
|
|
|
|
|
|
|
848
|
14
|
100
|
|
|
|
160
|
if (defined $self->{-min}) { |
849
|
6
|
|
|
|
|
19
|
my $min = _expand_dynamic_date($self->{-min}); |
850
|
|
|
|
|
|
|
!check_date(@$min) || (_date_cmp(\@date, $min) < 0) |
851
|
6
|
100
|
66
|
|
|
25
|
and return $self->msg(TOO_SMALL => _print_date($self->{-min})); |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
|
854
|
12
|
100
|
|
|
|
76
|
if (defined $self->{-max}) { |
855
|
3
|
|
|
|
|
8
|
my $max = _expand_dynamic_date($self->{-max}); |
856
|
|
|
|
|
|
|
!check_date(@$max) || (_date_cmp(\@date, $max) > 0) |
857
|
3
|
100
|
66
|
|
|
20
|
and return $self->msg(TOO_BIG => _print_date($self->{-max})); |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
|
860
|
11
|
100
|
|
|
|
26
|
if ($self->{-not_in}) { |
861
|
2
|
100
|
|
|
|
6
|
grep {_date_cmp(\@date, $_) == 0} @{$self->{-not_in}} |
|
4
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
5
|
|
862
|
|
|
|
|
|
|
and return $self->msg(EXCLUSION_SET => $data); |
863
|
|
|
|
|
|
|
} |
864
|
|
|
|
|
|
|
|
865
|
10
|
|
|
|
|
48
|
return; |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
#====================================================================== |
870
|
|
|
|
|
|
|
package Data::Domain::Time; |
871
|
|
|
|
|
|
|
#====================================================================== |
872
|
4
|
|
|
4
|
|
4670
|
use strict; |
|
4
|
|
|
|
|
40
|
|
|
4
|
|
|
|
|
111
|
|
873
|
4
|
|
|
4
|
|
23
|
use warnings; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
136
|
|
874
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
3154
|
|
875
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
my $time_regex = qr/^(\d\d?):?(\d\d?)?:?(\d\d?)?$/; |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
sub _valid_time { |
880
|
9
|
|
|
9
|
|
18
|
my ($h, $m, $s) = @_; |
881
|
9
|
|
50
|
|
|
18
|
$m ||= 0; |
882
|
9
|
|
50
|
|
|
31
|
$s ||= 0; |
883
|
9
|
|
66
|
|
|
57
|
return ($h <= 23 && $m <= 59 && $s <= 59); |
884
|
|
|
|
|
|
|
} |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
sub _expand_dynamic_time { |
888
|
16
|
|
|
16
|
|
21
|
my $time = shift; |
889
|
16
|
50
|
|
|
|
34
|
if (not ref $time) { |
890
|
0
|
0
|
|
|
|
0
|
$time eq 'now' or croak "unexpected time : $time"; |
891
|
0
|
|
|
|
|
0
|
$time = [(localtime)[2, 1, 0]]; |
892
|
|
|
|
|
|
|
} |
893
|
16
|
|
|
|
|
46
|
return $time; |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
sub _time_cmp { |
898
|
7
|
|
|
7
|
|
16
|
my ($t1, $t2) = map {_expand_dynamic_time($_)} @_; |
|
14
|
|
|
|
|
21
|
|
899
|
|
|
|
|
|
|
|
900
|
7
|
|
33
|
|
|
982
|
return $t1->[0] <=> $t2->[0] # hours |
901
|
|
|
|
|
|
|
|| ($t1->[1] || 0) <=> ($t2->[1] || 0) # minutes |
902
|
|
|
|
|
|
|
|| ($t1->[2] || 0) <=> ($t2->[2] || 0); # seconds |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
sub _print_time { |
906
|
2
|
|
|
2
|
|
5
|
my $time = _expand_dynamic_time(shift); |
907
|
2
|
100
|
|
|
|
5
|
return sprintf "%02d:%02d:%02d", map {$_ || 0} @$time; |
|
6
|
|
|
|
|
27
|
|
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
sub new { |
912
|
3
|
|
|
3
|
|
7
|
my $class = shift; |
913
|
3
|
|
|
|
|
9
|
my @options = qw/-range -min -max/; |
914
|
3
|
|
|
|
|
9
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
915
|
3
|
|
|
|
|
10
|
bless $self, $class; |
916
|
|
|
|
|
|
|
|
917
|
3
|
|
|
|
|
12
|
$self->_expand_range(qw/-range -min -max/); |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
# parse time boundaries |
920
|
3
|
|
|
|
|
6
|
for my $bound (qw/-min -max/) { |
921
|
6
|
100
|
66
|
|
|
28
|
if ($self->{$bound} and $self->{$bound} ne 'now') { |
922
|
4
|
|
|
|
|
28
|
my @time = ($self->{$bound} =~ $time_regex); |
923
|
4
|
50
|
33
|
|
|
17
|
@time && _valid_time(@time) |
924
|
|
|
|
|
|
|
or croak "invalid time ($bound): $self->{$bound}"; |
925
|
4
|
|
|
|
|
13
|
$self->{$bound} = \@time; |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
} |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
# check order of boundaries |
930
|
3
|
|
|
2
|
|
33
|
$self->_check_min_max(qw/-min -max/, sub {_time_cmp($_[0], $_[1]) <= 0}); |
|
2
|
|
|
|
|
7
|
|
931
|
|
|
|
|
|
|
|
932
|
2
|
|
|
|
|
20
|
return $self; |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
sub _inspect { |
937
|
6
|
|
|
6
|
|
12
|
my ($self, $data) = @_; |
938
|
|
|
|
|
|
|
|
939
|
6
|
|
|
|
|
55
|
my @t = ($data =~ $time_regex); |
940
|
6
|
100
|
100
|
|
|
30
|
@t and _valid_time(@t) |
941
|
|
|
|
|
|
|
or return $self->msg(INVALID => $data); |
942
|
|
|
|
|
|
|
|
943
|
4
|
100
|
|
|
|
13
|
if (defined $self->{-min}) { |
944
|
|
|
|
|
|
|
_time_cmp(\@t, $self->{-min}) < 0 |
945
|
3
|
100
|
|
|
|
9
|
and return $self->msg(TOO_SMALL => _print_time($self->{-min})); |
946
|
|
|
|
|
|
|
} |
947
|
|
|
|
|
|
|
|
948
|
3
|
100
|
|
|
|
7
|
if (defined $self->{-max}) { |
949
|
|
|
|
|
|
|
_time_cmp(\@t, $self->{-max}) > 0 |
950
|
2
|
100
|
|
|
|
5
|
and return $self->msg(TOO_BIG => _print_time($self->{-max})); |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
|
953
|
2
|
|
|
|
|
12
|
return; |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
#====================================================================== |
959
|
|
|
|
|
|
|
package Data::Domain::Handle; |
960
|
|
|
|
|
|
|
#====================================================================== |
961
|
4
|
|
|
4
|
|
39
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
118
|
|
962
|
4
|
|
|
4
|
|
23
|
use warnings; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
137
|
|
963
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
767
|
|
964
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
sub new { |
967
|
1
|
|
|
1
|
|
2
|
my $class = shift; |
968
|
1
|
|
|
|
|
5
|
my @options = (); |
969
|
1
|
|
|
|
|
4
|
my $self = Data::Domain::_parse_args(\@_, \@options); |
970
|
1
|
|
|
|
|
10
|
bless $self, $class; |
971
|
|
|
|
|
|
|
} |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
sub _inspect { |
974
|
3
|
|
|
3
|
|
7
|
my ($self, $data) = @_; |
975
|
3
|
100
|
|
|
|
19
|
Scalar::Util::openhandle($data) |
976
|
|
|
|
|
|
|
or return $self->msg(INVALID => ''); |
977
|
|
|
|
|
|
|
|
978
|
2
|
|
|
|
|
16
|
return; # otherwise OK, no error |
979
|
|
|
|
|
|
|
} |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
#====================================================================== |
985
|
|
|
|
|
|
|
package Data::Domain::Enum; |
986
|
|
|
|
|
|
|
#====================================================================== |
987
|
4
|
|
|
4
|
|
41
|
use strict; |
|
4
|
|
|
|
|
21
|
|
|
4
|
|
|
|
|
111
|
|
988
|
4
|
|
|
4
|
|
20
|
use warnings; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
138
|
|
989
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
214
|
|
990
|
4
|
|
|
4
|
|
33
|
use Try::Tiny; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
1267
|
|
991
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
sub new { |
994
|
5
|
|
|
5
|
|
11
|
my $class = shift; |
995
|
5
|
|
|
|
|
10
|
my @options = qw/-values/; |
996
|
5
|
|
|
|
|
17
|
my $self = Data::Domain::_parse_args(\@_, \@options, -values => 'arrayref'); |
997
|
5
|
|
|
|
|
16
|
bless $self, $class; |
998
|
|
|
|
|
|
|
|
999
|
5
|
50
|
|
5
|
|
42
|
try {@{$self->{-values}}} or croak "Enum : incorrect set of values"; |
|
5
|
|
|
|
|
133
|
|
|
5
|
|
|
|
|
48
|
|
1000
|
|
|
|
|
|
|
|
1001
|
5
|
100
|
|
|
|
72
|
not grep {! defined $_} @{$self->{-values}} |
|
19
|
|
|
|
|
168
|
|
|
5
|
|
|
|
|
14
|
|
1002
|
|
|
|
|
|
|
or croak "Enum : undefined element in values"; |
1003
|
|
|
|
|
|
|
|
1004
|
4
|
|
|
|
|
20
|
return $self; |
1005
|
|
|
|
|
|
|
} |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
sub _inspect { |
1009
|
6
|
|
|
6
|
|
19
|
my ($self, $data) = @_; |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
return $self->msg(NOT_IN_LIST => $data) |
1012
|
6
|
100
|
|
|
|
11
|
if not grep {$_ eq $data} @{$self->{-values}}; |
|
22
|
|
|
|
|
52
|
|
|
6
|
|
|
|
|
12
|
|
1013
|
|
|
|
|
|
|
|
1014
|
4
|
|
|
|
|
43
|
return; # otherwise OK, no error |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
#====================================================================== |
1019
|
|
|
|
|
|
|
package Data::Domain::List; |
1020
|
|
|
|
|
|
|
#====================================================================== |
1021
|
4
|
|
|
4
|
|
28
|
use strict; |
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
106
|
|
1022
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
112
|
|
1023
|
4
|
|
|
4
|
|
21
|
use Carp; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
237
|
|
1024
|
4
|
|
|
4
|
|
40
|
use List::MoreUtils qw/all/; |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
27
|
|
1025
|
4
|
|
|
4
|
|
4432
|
use Scalar::Does qw/does/; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
26
|
|
1026
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
sub new { |
1029
|
13
|
|
|
13
|
|
31
|
my $class = shift; |
1030
|
13
|
|
|
|
|
34
|
my @options = qw/-items -size -min_size -max_size -any -all/; |
1031
|
13
|
|
|
|
|
31
|
my $self = Data::Domain::_parse_args(\@_, \@options, -items => 'arrayref'); |
1032
|
13
|
|
|
|
|
30
|
bless $self, $class; |
1033
|
|
|
|
|
|
|
|
1034
|
13
|
|
|
|
|
48
|
$self->_expand_range(qw/-size -min_size -max_size/); |
1035
|
13
|
|
|
|
|
47
|
$self->_check_min_max(qw/-min_size -max_size <=/); |
1036
|
|
|
|
|
|
|
|
1037
|
12
|
100
|
|
|
|
44
|
if ($self->{-items}) { |
1038
|
5
|
50
|
|
|
|
18
|
does($self->{-items}, 'ARRAY') |
1039
|
|
|
|
|
|
|
or croak "invalid -items for Data::Domain::List"; |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
# if -items is given, then both -{min,max}_size cannot be shorter |
1042
|
5
|
|
|
|
|
116
|
for my $bound (qw/-min_size -max_size/) { |
1043
|
|
|
|
|
|
|
croak "$bound does not match -items" |
1044
|
10
|
50
|
33
|
|
|
29
|
if $self->{$bound} and $self->{$bound} < @{$self->{-items}}; |
|
0
|
|
|
|
|
0
|
|
1045
|
|
|
|
|
|
|
} |
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
# check that -all or -any are domains or lists of domains |
1049
|
12
|
|
|
|
|
24
|
for my $arg (qw/-all -any/) { |
1050
|
24
|
100
|
|
|
|
166
|
if (my $dom = $self->{$arg}) { |
1051
|
8
|
100
|
|
|
|
136
|
$dom = [$dom] unless does($dom, 'ARRAY'); |
1052
|
8
|
100
|
|
9
|
|
554
|
all {does($_, 'Data::Domain') || does($_, 'CODE')} @$dom |
|
9
|
50
|
|
|
|
39
|
|
1053
|
|
|
|
|
|
|
or croak "invalid arg to $arg in Data::Domain::List"; |
1054
|
|
|
|
|
|
|
} |
1055
|
|
|
|
|
|
|
} |
1056
|
|
|
|
|
|
|
|
1057
|
12
|
|
|
|
|
149
|
return $self; |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
sub _inspect { |
1062
|
38
|
|
|
38
|
|
65
|
my ($self, $data, $context) = @_; |
1063
|
4
|
|
|
4
|
|
3063
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
2285
|
|
1064
|
|
|
|
|
|
|
|
1065
|
38
|
100
|
|
|
|
101
|
does($data, 'ARRAY') |
1066
|
|
|
|
|
|
|
or return $self->msg(NOT_A_LIST => $data); |
1067
|
|
|
|
|
|
|
|
1068
|
37
|
100
|
100
|
|
|
820
|
if (defined $self->{-min_size} && @$data < $self->{-min_size}) { |
1069
|
1
|
|
|
|
|
5
|
return $self->msg(TOO_SHORT => $self->{-min_size}); |
1070
|
|
|
|
|
|
|
} |
1071
|
|
|
|
|
|
|
|
1072
|
36
|
100
|
100
|
|
|
93
|
if (defined $self->{-max_size} && @$data > $self->{-max_size}) { |
1073
|
1
|
|
|
|
|
4
|
return $self->msg(TOO_LONG => $self->{-max_size}); |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
|
1076
|
35
|
100
|
100
|
|
|
118
|
return unless $self->{-items} || $self->{-all} || $self->{-any}; |
|
|
|
100
|
|
|
|
|
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
# prepare context for calling lazy subdomains |
1079
|
33
|
|
100
|
|
|
350
|
$context ||= {root => $data, |
1080
|
|
|
|
|
|
|
flat => {}, |
1081
|
|
|
|
|
|
|
path => []}; |
1082
|
33
|
|
|
|
|
84
|
local $context->{list} = $data; |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
# initializing some variables |
1085
|
33
|
|
|
|
|
47
|
my @msgs; |
1086
|
|
|
|
|
|
|
my $has_invalid; |
1087
|
33
|
|
100
|
|
|
91
|
my $items = $self->{-items} || []; |
1088
|
33
|
|
|
|
|
53
|
my $n_items = @$items; |
1089
|
33
|
|
|
|
|
39
|
my $n_data = @$data; |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
# check the -items conditions |
1092
|
33
|
|
|
|
|
77
|
for (my $i = 0; $i < $n_items; $i++) { |
1093
|
50
|
|
|
|
|
89
|
local $context->{path} = [@{$context->{path}}, $i]; |
|
50
|
|
|
|
|
120
|
|
1094
|
50
|
50
|
|
|
|
130
|
my $subdomain = $self->_build_subdomain($items->[$i], $context) |
1095
|
|
|
|
|
|
|
or next; |
1096
|
50
|
|
|
|
|
860
|
$msgs[$i] = $subdomain->inspect($data->[$i], $context); |
1097
|
50
|
|
100
|
|
|
236
|
$has_invalid ||= $msgs[$i]; |
1098
|
|
|
|
|
|
|
} |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
# check the -all condition (can be a single domain or an arrayref of domains) |
1101
|
33
|
100
|
|
|
|
81
|
if (my $all = $self->{-all}) { |
1102
|
8
|
50
|
|
|
|
116
|
$all = [$all] unless does($all, 'ARRAY'); |
1103
|
8
|
|
|
|
|
450
|
my $n_all = @$all; |
1104
|
8
|
|
|
|
|
24
|
for (my $i = $n_items, my $j = 0; # $i iterates over @$data, $j over @$all |
1105
|
|
|
|
|
|
|
$i < $n_data; |
1106
|
|
|
|
|
|
|
$i++, $j = ($j + 1) % $n_all) { |
1107
|
28
|
|
|
|
|
40
|
local $context->{path} = [@{$context->{path}}, $i]; |
|
28
|
|
|
|
|
73
|
|
1108
|
28
|
|
|
|
|
440
|
my $subdomain = $self->_build_subdomain($all->[$j], $context); |
1109
|
28
|
|
|
|
|
84
|
$msgs[$i] = $subdomain->inspect($data->[$i], $context); |
1110
|
28
|
|
100
|
|
|
162
|
$has_invalid ||= $msgs[$i]; |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
# stop here if there was any error message |
1115
|
33
|
100
|
|
|
|
111
|
return \@msgs if $has_invalid; |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
# all other conditions were good, now check the "any" conditions |
1118
|
24
|
100
|
|
|
|
57
|
if (my $any = $self->{-any}) { |
1119
|
13
|
100
|
|
|
|
148
|
$any = [$any] unless does($any, 'ARRAY'); |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
# there must be data to inspect |
1122
|
|
|
|
|
|
|
$n_data > $n_items |
1123
|
13
|
100
|
33
|
|
|
741
|
or return $self->msg(ANY => ($any->[0]{-name} || $any->[0]->subclass)); |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
# inspect the remaining data for all 'any' conditions |
1126
|
|
|
|
|
|
|
CONDITION: |
1127
|
12
|
|
|
|
|
26
|
foreach my $condition (@$any) { |
1128
|
15
|
|
|
|
|
19
|
my $subdomain; |
1129
|
15
|
|
|
|
|
34
|
for (my $i = $n_items; $i < $n_data; $i++) { |
1130
|
31
|
|
|
|
|
41
|
local $context->{path} = [@{$context->{path}}, $i]; |
|
31
|
|
|
|
|
79
|
|
1131
|
31
|
|
|
|
|
63
|
$subdomain = $self->_build_subdomain($condition, $context); |
1132
|
31
|
|
|
|
|
68
|
my $error = $subdomain->inspect($data->[$i], $context); |
1133
|
31
|
100
|
|
|
|
112
|
next CONDITION if not $error; |
1134
|
|
|
|
|
|
|
} |
1135
|
4
|
|
33
|
|
|
14
|
return $self->msg(ANY => ($subdomain->{-name} || $subdomain->subclass)); |
1136
|
|
|
|
|
|
|
} |
1137
|
|
|
|
|
|
|
} |
1138
|
|
|
|
|
|
|
|
1139
|
19
|
|
|
|
|
131
|
return; # OK, no error |
1140
|
|
|
|
|
|
|
} |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
#====================================================================== |
1144
|
|
|
|
|
|
|
package Data::Domain::Struct; |
1145
|
|
|
|
|
|
|
#====================================================================== |
1146
|
4
|
|
|
4
|
|
30
|
use strict; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
159
|
|
1147
|
4
|
|
|
4
|
|
26
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
109
|
|
1148
|
4
|
|
|
4
|
|
40
|
use Carp; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
250
|
|
1149
|
4
|
|
|
4
|
|
26
|
use Scalar::Does qw/does/; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
23
|
|
1150
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
sub new { |
1153
|
14
|
|
|
14
|
|
27
|
my $class = shift; |
1154
|
14
|
|
|
|
|
31
|
my @options = qw/-fields -exclude -keys -values/; |
1155
|
14
|
|
|
|
|
37
|
my $self = Data::Domain::_parse_args(\@_, \@options, -fields => 'arrayref'); |
1156
|
14
|
|
|
|
|
29
|
bless $self, $class; |
1157
|
|
|
|
|
|
|
|
1158
|
14
|
|
100
|
|
|
114
|
my $fields = $self->{-fields} || []; |
1159
|
14
|
100
|
|
|
|
42
|
if (does($fields, 'ARRAY')) { |
|
|
50
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
# transform arrayref into hashref plus an ordered list of keys |
1161
|
13
|
|
|
|
|
940
|
$self->{-fields_list} = []; |
1162
|
13
|
|
|
|
|
30
|
$self->{-fields} = {}; |
1163
|
13
|
|
|
|
|
55
|
for (my $i = 0; $i < @$fields; $i += 2) { |
1164
|
22
|
|
|
|
|
49
|
my ($key, $val) = ($fields->[$i], $fields->[$i+1]); |
1165
|
22
|
|
|
|
|
32
|
push @{$self->{-fields_list}}, $key; |
|
22
|
|
|
|
|
37
|
|
1166
|
22
|
|
|
|
|
74
|
$self->{-fields}{$key} = $val; |
1167
|
|
|
|
|
|
|
} |
1168
|
|
|
|
|
|
|
} |
1169
|
|
|
|
|
|
|
elsif (does($fields, 'HASH')) { |
1170
|
|
|
|
|
|
|
# keep given hashref, add list of keys |
1171
|
1
|
|
|
|
|
46
|
$self->{-fields_list} = [keys %$fields]; |
1172
|
|
|
|
|
|
|
} |
1173
|
|
|
|
|
|
|
else { |
1174
|
0
|
|
|
|
|
0
|
croak "invalid data for -fields option"; |
1175
|
|
|
|
|
|
|
} |
1176
|
|
|
|
|
|
|
|
1177
|
|
|
|
|
|
|
# check that -exclude is an arrayref or a regex or a string |
1178
|
14
|
100
|
|
|
|
49
|
if (my $exclude = $self->{-exclude}) { |
1179
|
3
|
50
|
100
|
|
|
9
|
does($exclude, 'ARRAY') || does($exclude, 'Regexp') || !ref($exclude) |
|
|
|
66
|
|
|
|
|
1180
|
|
|
|
|
|
|
or croak "invalid data for -exclude option"; |
1181
|
|
|
|
|
|
|
} |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
|
1184
|
|
|
|
|
|
|
# check that -keys or -values are List domains |
1185
|
14
|
|
|
|
|
184
|
for my $arg (qw/-keys -values/) { |
1186
|
28
|
100
|
|
|
|
90
|
if (my $dom = $self->{$arg}) { |
1187
|
2
|
50
|
33
|
|
|
44
|
does($dom, 'Data::Domain::List') or does($dom, 'CODE') |
1188
|
|
|
|
|
|
|
or croak "$arg in Data::Domain::Struct should be a List domain"; |
1189
|
|
|
|
|
|
|
} |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
|
1192
|
14
|
|
|
|
|
115
|
return $self; |
1193
|
|
|
|
|
|
|
} |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
sub _inspect { |
1197
|
137
|
|
|
137
|
|
293
|
my ($self, $data, $context) = @_; |
1198
|
4
|
|
|
4
|
|
3062
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
1895
|
|
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
# check that $data is a hashref |
1201
|
137
|
100
|
|
|
|
323
|
does($data, 'HASH') |
1202
|
|
|
|
|
|
|
or return $self->msg(NOT_A_HASH => $data); |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
# check if there are any forbidden fields |
1205
|
135
|
100
|
|
|
|
3299
|
if (my $exclude = $self->{-exclude}) { |
1206
|
|
|
|
|
|
|
FIELD: |
1207
|
9
|
|
|
|
|
37
|
foreach my $field (keys %$data) { |
1208
|
13
|
100
|
|
|
|
73
|
next FIELD if $self->{-fields}{$field}; |
1209
|
|
|
|
|
|
|
|
1210
|
8
|
100
|
100
|
|
|
67
|
return $self->msg(FORBIDDEN_FIELD => $field) |
1211
|
|
|
|
|
|
|
if match::simple::match($field, $exclude) |
1212
|
|
|
|
|
|
|
or match::simple::match($exclude, ['*', 'all']); |
1213
|
|
|
|
|
|
|
} |
1214
|
|
|
|
|
|
|
} |
1215
|
|
|
|
|
|
|
|
1216
|
129
|
|
|
|
|
250
|
my %msgs; |
1217
|
|
|
|
|
|
|
my $has_invalid; |
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
# prepare context for calling lazy subdomains |
1220
|
129
|
|
100
|
|
|
365
|
$context ||= {root => $data, |
1221
|
|
|
|
|
|
|
flat => {}, |
1222
|
|
|
|
|
|
|
list => [], |
1223
|
|
|
|
|
|
|
path => []}; |
1224
|
129
|
|
|
|
|
176
|
local $context->{flat} = {%{$context->{flat}}, %$data}; |
|
129
|
|
|
|
|
792
|
|
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
# check fields of the domain |
1227
|
129
|
|
|
|
|
241
|
foreach my $field (@{$self->{-fields_list}}) { |
|
129
|
|
|
|
|
286
|
|
1228
|
354
|
|
|
|
|
481
|
local $context->{path} = [@{$context->{path}}, $field]; |
|
354
|
|
|
|
|
3591
|
|
1229
|
354
|
|
|
|
|
722
|
my $field_spec = $self->{-fields}{$field}; |
1230
|
354
|
|
|
|
|
782
|
my $subdomain = $self->_build_subdomain($field_spec, $context); |
1231
|
353
|
|
|
|
|
1565
|
my $msg = $subdomain->inspect($data->{$field}, $context); |
1232
|
254
|
100
|
|
|
|
511
|
$msgs{$field} = $msg if $msg; |
1233
|
254
|
|
100
|
|
|
1358
|
$has_invalid ||= $msg; |
1234
|
|
|
|
|
|
|
} |
1235
|
|
|
|
|
|
|
|
1236
|
|
|
|
|
|
|
# check the List domain for keys |
1237
|
29
|
100
|
|
|
|
77
|
if (my $keys_dom = $self->{-keys}) { |
1238
|
3
|
|
|
|
|
70
|
local $context->{path} = [@{$context->{path}}, "-keys"]; |
|
3
|
|
|
|
|
11
|
|
1239
|
3
|
|
|
|
|
21
|
my $subdomain = $self->_build_subdomain($keys_dom, $context); |
1240
|
3
|
100
|
|
|
|
12
|
$msgs{-keys} = $subdomain->inspect([keys %$data], $context) |
1241
|
|
|
|
|
|
|
and $has_invalid = 1; |
1242
|
|
|
|
|
|
|
} |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
# check the List domain for values |
1245
|
29
|
100
|
|
|
|
101
|
if (my $values_dom = $self->{-values}) { |
1246
|
3
|
|
|
|
|
51
|
local $context->{path} = [@{$context->{path}}, "-values"]; |
|
3
|
|
|
|
|
10
|
|
1247
|
3
|
|
|
|
|
10
|
my $subdomain = $self->_build_subdomain($values_dom, $context); |
1248
|
3
|
100
|
|
|
|
9
|
$msgs{-values} = $subdomain->inspect([values %$data], $context) |
1249
|
|
|
|
|
|
|
and $has_invalid = 1; |
1250
|
|
|
|
|
|
|
} |
1251
|
|
|
|
|
|
|
|
1252
|
29
|
100
|
|
|
|
251
|
return $has_invalid ? \%msgs : undef; |
1253
|
|
|
|
|
|
|
} |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
#====================================================================== |
1256
|
|
|
|
|
|
|
package Data::Domain::One_of; |
1257
|
|
|
|
|
|
|
#====================================================================== |
1258
|
4
|
|
|
4
|
|
36
|
use strict; |
|
4
|
|
|
|
|
17
|
|
|
4
|
|
|
|
|
107
|
|
1259
|
4
|
|
|
4
|
|
85
|
use warnings; |
|
4
|
|
|
|
|
13
|
|
|
4
|
|
|
|
|
136
|
|
1260
|
4
|
|
|
4
|
|
24
|
use Carp; |
|
4
|
|
|
|
|
18
|
|
|
4
|
|
|
|
|
864
|
|
1261
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1262
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
sub new { |
1264
|
2
|
|
|
2
|
|
5
|
my $class = shift; |
1265
|
2
|
|
|
|
|
5
|
my @options = qw/-options/; |
1266
|
2
|
|
|
|
|
7
|
my $self = Data::Domain::_parse_args(\@_, \@options, -options => 'arrayref'); |
1267
|
2
|
|
|
|
|
6
|
bless $self, $class; |
1268
|
|
|
|
|
|
|
|
1269
|
2
|
50
|
|
|
|
62
|
Scalar::Does::does($self->{-options}, 'ARRAY') |
1270
|
|
|
|
|
|
|
or croak "One_of: invalid options"; |
1271
|
|
|
|
|
|
|
|
1272
|
2
|
|
|
|
|
60
|
return $self; |
1273
|
|
|
|
|
|
|
} |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
sub _inspect { |
1277
|
213
|
|
|
213
|
|
374
|
my ($self, $data, $context) = @_; |
1278
|
213
|
|
|
|
|
288
|
my @msgs; |
1279
|
4
|
|
|
4
|
|
27
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
20
|
|
|
4
|
|
|
|
|
551
|
|
1280
|
|
|
|
|
|
|
|
1281
|
213
|
|
|
|
|
301
|
for my $subdomain (@{$self->{-options}}) { |
|
213
|
|
|
|
|
436
|
|
1282
|
321
|
100
|
|
|
|
1397
|
my $msg = $subdomain->inspect($data, $context) |
1283
|
|
|
|
|
|
|
or return; # $subdomain was successful |
1284
|
112
|
|
|
|
|
264
|
push @msgs, $msg; |
1285
|
|
|
|
|
|
|
} |
1286
|
4
|
|
|
|
|
30
|
return \@msgs; |
1287
|
|
|
|
|
|
|
} |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
|
1290
|
|
|
|
|
|
|
#====================================================================== |
1291
|
|
|
|
|
|
|
package Data::Domain::All_of; |
1292
|
|
|
|
|
|
|
#====================================================================== |
1293
|
4
|
|
|
4
|
|
28
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
102
|
|
1294
|
4
|
|
|
4
|
|
19
|
use warnings; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
131
|
|
1295
|
4
|
|
|
4
|
|
24
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
850
|
|
1296
|
|
|
|
|
|
|
our @ISA = 'Data::Domain'; |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
sub new { |
1299
|
1
|
|
|
1
|
|
7
|
my $class = shift; |
1300
|
1
|
|
|
|
|
4
|
my @options = qw/-options/; |
1301
|
1
|
|
|
|
|
16
|
my $self = Data::Domain::_parse_args(\@_, \@options, -options => 'arrayref'); |
1302
|
1
|
|
|
|
|
3
|
bless $self, $class; |
1303
|
|
|
|
|
|
|
|
1304
|
1
|
50
|
|
|
|
40
|
Scalar::Does::does($self->{-options}, 'ARRAY') |
1305
|
|
|
|
|
|
|
or croak "All_of: invalid options"; |
1306
|
|
|
|
|
|
|
|
1307
|
1
|
|
|
|
|
31
|
return $self; |
1308
|
|
|
|
|
|
|
} |
1309
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
|
1311
|
|
|
|
|
|
|
sub _inspect { |
1312
|
3
|
|
|
3
|
|
7
|
my ($self, $data, $context) = @_; |
1313
|
3
|
|
|
|
|
5
|
my @msgs; |
1314
|
4
|
|
|
4
|
|
50
|
no warnings 'recursion'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
575
|
|
1315
|
|
|
|
|
|
|
|
1316
|
3
|
|
|
|
|
5
|
for my $subdomain (@{$self->{-options}}) { |
|
3
|
|
|
|
|
8
|
|
1317
|
6
|
|
|
|
|
13
|
my $msg = $subdomain->inspect($data, $context); |
1318
|
6
|
100
|
|
|
|
18
|
push @msgs, $msg if $msg; # subdomain failed |
1319
|
|
|
|
|
|
|
} |
1320
|
3
|
100
|
|
|
|
19
|
return @msgs ? \@msgs : undef; |
1321
|
|
|
|
|
|
|
} |
1322
|
|
|
|
|
|
|
|
1323
|
|
|
|
|
|
|
|
1324
|
|
|
|
|
|
|
#====================================================================== |
1325
|
|
|
|
|
|
|
1; |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
|
1328
|
|
|
|
|
|
|
__END__ |