| 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__ |