line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# |
2
|
|
|
|
|
|
|
# Web::DataService::Node |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# This module provides a role that is used by 'Web::DataService'. It implements |
5
|
|
|
|
|
|
|
# routines for defining and querying data service nodes. |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
# Author: Michael McClennen |
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
17
|
use strict; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
94
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Web::DataService::Node; |
12
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
13
|
use Carp 'croak'; |
|
2
|
|
|
|
|
12
|
|
|
2
|
|
|
|
|
106
|
|
14
|
2
|
|
|
2
|
|
12
|
use Scalar::Util 'reftype'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
86
|
|
15
|
|
|
|
|
|
|
|
16
|
2
|
|
|
2
|
|
1052
|
use Moo::Role; |
|
2
|
|
|
|
|
29320
|
|
|
2
|
|
|
|
|
11
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our (%NODE_DEF) = ( path => 'ignore', |
20
|
|
|
|
|
|
|
disabled => 'single', |
21
|
|
|
|
|
|
|
undocumented => 'single', |
22
|
|
|
|
|
|
|
place => 'single', |
23
|
|
|
|
|
|
|
list => 'single', |
24
|
|
|
|
|
|
|
title => 'single', |
25
|
|
|
|
|
|
|
usage => 'single', |
26
|
|
|
|
|
|
|
file_dir => 'single', |
27
|
|
|
|
|
|
|
file_path => 'single', |
28
|
|
|
|
|
|
|
role => 'single', |
29
|
|
|
|
|
|
|
method => 'single', |
30
|
|
|
|
|
|
|
arg => 'single', |
31
|
|
|
|
|
|
|
node_tag => 'set', |
32
|
|
|
|
|
|
|
node_data => 'single', |
33
|
|
|
|
|
|
|
ruleset => 'single', |
34
|
|
|
|
|
|
|
output => 'list', |
35
|
|
|
|
|
|
|
output_label => 'single', |
36
|
|
|
|
|
|
|
optional_output => 'single', |
37
|
|
|
|
|
|
|
summary => 'single', |
38
|
|
|
|
|
|
|
public_access => 'single', |
39
|
|
|
|
|
|
|
default_format => 'single', |
40
|
|
|
|
|
|
|
default_limit => 'single', |
41
|
|
|
|
|
|
|
default_header => 'single', |
42
|
|
|
|
|
|
|
default_datainfo => 'single', |
43
|
|
|
|
|
|
|
default_count => 'single', |
44
|
|
|
|
|
|
|
default_linebreak => 'single', |
45
|
|
|
|
|
|
|
default_save_filename => 'single', |
46
|
|
|
|
|
|
|
stream_theshold => 'single', |
47
|
|
|
|
|
|
|
before_execute_hook => 'hook', |
48
|
|
|
|
|
|
|
before_config_hook => 'hook', |
49
|
|
|
|
|
|
|
before_setup_hook => 'hook', |
50
|
|
|
|
|
|
|
before_operation_hook => 'hook', |
51
|
|
|
|
|
|
|
before_output_hook => 'hook', |
52
|
|
|
|
|
|
|
before_record_hook => 'hook', |
53
|
|
|
|
|
|
|
after_serialize_hook => 'hook', |
54
|
|
|
|
|
|
|
post_configure_hook => 'hook', # deprecated |
55
|
|
|
|
|
|
|
use_cache => 'single', |
56
|
|
|
|
|
|
|
allow_method => 'set', |
57
|
|
|
|
|
|
|
allow_format => 'set', |
58
|
|
|
|
|
|
|
allow_vocab => 'set', |
59
|
|
|
|
|
|
|
doc_string => 'single', |
60
|
|
|
|
|
|
|
doc_template => 'single', |
61
|
|
|
|
|
|
|
doc_default_template => 'single', |
62
|
|
|
|
|
|
|
doc_default_op_template => 'single', |
63
|
|
|
|
|
|
|
doc_defs => 'single', |
64
|
|
|
|
|
|
|
doc_header => 'single', |
65
|
|
|
|
|
|
|
doc_footer => 'single', |
66
|
|
|
|
|
|
|
); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
our (%NODE_NONHERITABLE) = ( title => 1, |
70
|
|
|
|
|
|
|
doc_string => 1, |
71
|
|
|
|
|
|
|
doc_template => 1, |
72
|
|
|
|
|
|
|
place => 1, |
73
|
|
|
|
|
|
|
usage => 1, |
74
|
|
|
|
|
|
|
); |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
our (%NODE_ATTR_DEFAULT) = ( default_header => 1 ); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
our (%EXTENDED_DEF) = ( path => 1, |
79
|
|
|
|
|
|
|
type => 1, |
80
|
|
|
|
|
|
|
name => 1, |
81
|
|
|
|
|
|
|
disp => 1, |
82
|
|
|
|
|
|
|
); |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# define_node ( attrs... ) |
85
|
|
|
|
|
|
|
# |
86
|
|
|
|
|
|
|
# Set up a "path" entry, representing a complete or partial URL path. This |
87
|
|
|
|
|
|
|
# path should have a documentation page, but if one is not defined a template |
88
|
|
|
|
|
|
|
# page will be used along with any documentation strings given in this call. |
89
|
|
|
|
|
|
|
# Any path which represents an operation must be given an 'op' attribute. |
90
|
|
|
|
|
|
|
# |
91
|
|
|
|
|
|
|
# An error will be signalled unless the "parent" path is already defined. In |
92
|
|
|
|
|
|
|
# other words, you cannot define 'a/b/c' unless 'a/b' is defined first. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
sub define_node { |
95
|
|
|
|
|
|
|
|
96
|
1
|
|
|
1
|
0
|
16
|
my $ds = shift; |
97
|
|
|
|
|
|
|
|
98
|
1
|
|
|
|
|
4
|
my ($package, $filename, $line) = caller; |
99
|
|
|
|
|
|
|
|
100
|
1
|
|
|
|
|
3
|
my ($last_node); |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Now we go through the rest of the arguments. Hashrefs define new |
103
|
|
|
|
|
|
|
# nodes, while strings add to the documentation of the node |
104
|
|
|
|
|
|
|
# whose definition they follow. |
105
|
|
|
|
|
|
|
|
106
|
1
|
|
|
|
|
4
|
foreach my $item (@_) |
107
|
|
|
|
|
|
|
{ |
108
|
|
|
|
|
|
|
# A hashref defines a new directory. |
109
|
|
|
|
|
|
|
|
110
|
2
|
50
|
|
|
|
6
|
if ( ref $item eq 'HASH' ) |
|
|
0
|
|
|
|
|
|
111
|
|
|
|
|
|
|
{ |
112
|
|
|
|
|
|
|
croak "define_node: each definition must include a non-empty value for 'path'\n" |
113
|
2
|
50
|
33
|
|
|
10
|
unless defined $item->{path} && $item->{path} ne ''; |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
croak "define_node: invalid path '$item->{path}'\n" if $item->{path} ne '/' && |
116
|
2
|
50
|
66
|
|
|
18
|
$item->{path} =~ qr{ ^ / | / $ | // | [?#] }xs; |
117
|
|
|
|
|
|
|
|
118
|
2
|
|
|
|
|
9
|
$last_node = $ds->_create_path_node($item, $filename, $line); |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
elsif ( not ref $item ) |
122
|
|
|
|
|
|
|
{ |
123
|
0
|
|
|
|
|
0
|
$ds->add_node_doc($last_node, $item); |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
else |
127
|
|
|
|
|
|
|
{ |
128
|
0
|
|
|
|
|
0
|
croak "define_node: the arguments must be a list of hashrefs and strings\n"; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
1
|
50
|
|
|
|
6
|
croak "define_node: arguments must include at least one hashref of attributes\n" |
133
|
|
|
|
|
|
|
unless $last_node; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# _create_path_node ( attrs, filename, line ) |
139
|
|
|
|
|
|
|
# |
140
|
|
|
|
|
|
|
# Create a new node representing the specified path. Attributes are |
141
|
|
|
|
|
|
|
# inherited, as follows: 'a/b/c' inherits from 'a/b', which inherits from 'a', |
142
|
|
|
|
|
|
|
# which inherits from '/'. If 'a/b' does not exist, then 'a/b/c' inherits |
143
|
|
|
|
|
|
|
# directly from 'a'. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub _create_path_node { |
146
|
|
|
|
|
|
|
|
147
|
2
|
|
|
2
|
|
5
|
my ($ds, $new_attrs, $filename, $line) = @_; |
148
|
|
|
|
|
|
|
|
149
|
2
|
|
|
|
|
3
|
my $path = $new_attrs->{path}; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Make sure this path was not already defined by a previous call. |
152
|
|
|
|
|
|
|
|
153
|
2
|
50
|
|
|
|
9
|
if ( defined $ds->{path_defs}{$path} ) |
154
|
|
|
|
|
|
|
{ |
155
|
0
|
|
|
|
|
0
|
my $filename = $ds->{path_defs}{$path}{filename}; |
156
|
0
|
|
|
|
|
0
|
my $line = $ds->{path_defs}{$path}{line}; |
157
|
0
|
|
|
|
|
0
|
croak "define_node: '$path' was already defined at line $line of $filename\n"; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
else |
161
|
|
|
|
|
|
|
{ |
162
|
2
|
|
|
|
|
8
|
$ds->{path_defs}{$path} = { filename => $filename, line => $line }; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Create a new node to hold the path attributes. |
166
|
|
|
|
|
|
|
|
167
|
2
|
|
|
|
|
5
|
my $node_attrs = { disabled => 0 }; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# Then apply the newly specified attributes, checking any list or set |
170
|
|
|
|
|
|
|
# values. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
KEY: |
173
|
2
|
|
|
|
|
7
|
foreach my $key ( keys %$new_attrs ) |
174
|
|
|
|
|
|
|
{ |
175
|
|
|
|
|
|
|
croak "define_node '$path': unknown attribute '$key'\n" |
176
|
6
|
50
|
|
|
|
15
|
unless $NODE_DEF{$key}; |
177
|
|
|
|
|
|
|
|
178
|
6
|
|
|
|
|
8
|
my $value = $new_attrs->{$key}; |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
# If the value is undefined or the empty string, store it and go on to |
181
|
|
|
|
|
|
|
# the next. This means that the value should be considered unset. |
182
|
|
|
|
|
|
|
|
183
|
6
|
50
|
33
|
|
|
30
|
if ( ! defined $value || $value eq '' ) |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
184
|
|
|
|
|
|
|
{ |
185
|
0
|
|
|
|
|
0
|
$node_attrs->{$key} = $value; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# If the attribute takes a single value, then set the value as |
189
|
|
|
|
|
|
|
# specified. |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
elsif ( $NODE_DEF{$key} eq 'single' ) |
192
|
|
|
|
|
|
|
{ |
193
|
3
|
|
|
|
|
6
|
$node_attrs->{$key} = $value; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# If it takes a hook value, then the value can be either a list or a |
197
|
|
|
|
|
|
|
# singleton. In either case, each value must be either a code ref or |
198
|
|
|
|
|
|
|
# a string. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
elsif ( $NODE_DEF{$key} eq 'hook' ) |
201
|
|
|
|
|
|
|
{ |
202
|
0
|
0
|
|
|
|
0
|
if ( ref $value eq 'ARRAY' ) |
203
|
|
|
|
|
|
|
{ |
204
|
0
|
|
|
|
|
0
|
foreach my $v ( @$value ) |
205
|
|
|
|
|
|
|
{ |
206
|
0
|
0
|
0
|
|
|
0
|
croak "define_node '$path': $key has invalid value '$v', must be a code ref or string\n" |
207
|
|
|
|
|
|
|
unless ref $v eq 'CODE' || ! ref $v; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
else |
212
|
|
|
|
|
|
|
{ |
213
|
0
|
0
|
0
|
|
|
0
|
croak "define_node '$path': $key has invalid value '$value', must be a code ref or string\n" |
214
|
|
|
|
|
|
|
unless ref $value eq 'CODE' || ! ref $value; |
215
|
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
0
|
$value = [ $value ]; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
0
|
$node_attrs->{$key} = $value; |
220
|
0
|
|
|
|
|
0
|
$ds->{hook_enabled}{$key} = 1; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
# If the attribute takes a set value, then check that it is |
224
|
|
|
|
|
|
|
# either a single value or a comma-separated list. If any of the |
225
|
|
|
|
|
|
|
# values begin with + or -, then all must. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
elsif ( $NODE_DEF{$key} eq 'set' ) |
228
|
|
|
|
|
|
|
{ |
229
|
0
|
0
|
0
|
|
|
0
|
unless ( $value =~ qr{ ^ (?> [\w.:][\w.:-]* | \s*,\s* )* $ }xs || |
230
|
|
|
|
|
|
|
$value =~ qr{ ^ (?> [+-][\w.:][\w.:-]* | \s*,\s* )* $ }xs ) |
231
|
|
|
|
|
|
|
{ |
232
|
0
|
|
|
|
|
0
|
croak "define_node '$path': $key has invalid value '$value'\n"; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
0
|
|
|
|
|
0
|
$node_attrs->{$key} = $value; |
236
|
0
|
0
|
|
|
|
0
|
$ds->{path_compose}{$path}{$key} = 1 if $value =~ qr{ ^ (?> \s*,\s* )* [+-] }xs; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# If the attribute takes a list value, then check that it is either a |
240
|
|
|
|
|
|
|
# single value or a comma-separated list. |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
elsif ( $NODE_DEF{$key} eq 'list' ) |
243
|
|
|
|
|
|
|
{ |
244
|
1
|
50
|
|
|
|
16
|
unless ( $value =~ qr{ ^ (?> [\w.:-]+ | \s*,\s* )+ $ }xs ) |
245
|
|
|
|
|
|
|
{ |
246
|
0
|
|
|
|
|
0
|
croak "define_node '$path': $key has invalid value '$value'\n"; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
1
|
|
|
|
|
4
|
$node_attrs->{$key} = $value; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# Otherwise this attribute is ignored |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
else |
255
|
|
|
|
|
|
|
{ |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Install the node. |
260
|
|
|
|
|
|
|
|
261
|
2
|
|
|
|
|
5
|
$ds->{node_attrs}{$path} = $node_attrs; |
262
|
|
|
|
|
|
|
|
263
|
2
|
|
|
|
|
3
|
my $place = $node_attrs->{place}; |
264
|
|
|
|
|
|
|
|
265
|
2
|
50
|
|
|
|
6
|
if ( defined $place ) |
266
|
|
|
|
|
|
|
{ |
267
|
0
|
|
0
|
|
|
0
|
my $list = $node_attrs->{list} // $ds->path_parent($path); |
268
|
|
|
|
|
|
|
|
269
|
2
|
|
|
2
|
|
3616
|
no warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
511
|
|
270
|
0
|
0
|
0
|
|
|
0
|
if ( $place > 0 && defined $list && $list ne '' ) |
|
|
0
|
0
|
|
|
|
|
271
|
|
|
|
|
|
|
{ |
272
|
0
|
|
|
|
|
0
|
push @{$ds->{node_list}{$list}{$place}}, { path => $path }; |
|
0
|
|
|
|
|
0
|
|
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
elsif ( $place ne '0' ) |
276
|
|
|
|
|
|
|
{ |
277
|
0
|
|
|
|
|
0
|
croak "define_node '$path': invalid value for 'place' - must be a number\n"; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# Now check the attributes to make sure they are consistent: |
282
|
|
|
|
|
|
|
|
283
|
2
|
|
|
|
|
7
|
$ds->_check_path_node($path); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# If one of the attributes is 'role', create a new request execution class |
286
|
|
|
|
|
|
|
# for this role unless we are in "one request" mode. |
287
|
|
|
|
|
|
|
|
288
|
2
|
|
|
|
|
16
|
my $role = $ds->node_attr($path, 'role'); |
289
|
|
|
|
|
|
|
|
290
|
2
|
50
|
33
|
|
|
8
|
if ( $role and not $Web::DataService::ONE_REQUEST ) |
291
|
|
|
|
|
|
|
{ |
292
|
0
|
|
|
|
|
0
|
$ds->execution_class($role); |
293
|
0
|
|
|
|
|
0
|
$ds->documentation_class($role); |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
# Now return the new node. |
297
|
|
|
|
|
|
|
|
298
|
2
|
|
|
|
|
7
|
return $node_attrs; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
sub _check_path_node { |
303
|
|
|
|
|
|
|
|
304
|
2
|
|
|
2
|
|
4
|
my ($ds, $path) = @_; |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Throw an error if 'role' doesn't specify an existing module. |
307
|
|
|
|
|
|
|
|
308
|
2
|
|
|
|
|
6
|
my $role = $ds->node_attr($path, 'role'); |
309
|
|
|
|
|
|
|
|
310
|
2
|
50
|
|
|
|
14
|
if ( $role ) |
311
|
|
|
|
|
|
|
{ |
312
|
2
|
|
|
2
|
|
18
|
no strict 'refs'; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
875
|
|
313
|
|
|
|
|
|
|
|
314
|
0
|
0
|
|
|
|
0
|
croak "define_node '$path': the value of 'role' should be a package name, not a file name\n" |
315
|
|
|
|
|
|
|
if $role =~ qr { [.] pm $ }xs; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
croak "define_node '$path': you must load the module '$role' before using it as the value of 'role'\n" |
318
|
0
|
0
|
|
|
|
0
|
unless %{ "${role}::" }; |
|
0
|
|
|
|
|
0
|
|
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# Throw an error if 'method' doesn't specify an existing method |
322
|
|
|
|
|
|
|
# implemented by this role. |
323
|
|
|
|
|
|
|
|
324
|
2
|
|
|
|
|
6
|
my $method = $ds->node_attr($path, 'method'); |
325
|
|
|
|
|
|
|
|
326
|
2
|
50
|
|
|
|
12
|
if ( $method ) |
327
|
|
|
|
|
|
|
{ |
328
|
0
|
0
|
|
|
|
0
|
croak "define_node '$path': method '$method' is not valid unless you also specify its package using 'role'\n" |
329
|
|
|
|
|
|
|
unless defined $role; |
330
|
|
|
|
|
|
|
|
331
|
0
|
0
|
|
|
|
0
|
croak "define_node '$path': '$method' must be a method implemented by '$role'\n" |
332
|
|
|
|
|
|
|
unless $role->can($method); |
333
|
|
|
|
|
|
|
} |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# Throw an error if more than one of 'file_path', 'file_dir', 'method' are |
336
|
|
|
|
|
|
|
# set. |
337
|
|
|
|
|
|
|
|
338
|
2
|
|
|
|
|
5
|
my $attr_count = 0; |
339
|
|
|
|
|
|
|
|
340
|
2
|
50
|
|
|
|
5
|
$attr_count++ if $method; |
341
|
2
|
50
|
|
|
|
5
|
$attr_count++ if $ds->node_attr($path, 'file_dir'); |
342
|
2
|
50
|
|
|
|
6
|
$attr_count++ if $ds->node_attr($path, 'file_path'); |
343
|
|
|
|
|
|
|
|
344
|
2
|
50
|
33
|
|
|
18
|
if ( $method && $attr_count > 1 ) |
|
|
50
|
|
|
|
|
|
345
|
|
|
|
|
|
|
{ |
346
|
0
|
|
|
|
|
0
|
croak "define_node '$path': you may only specify one of 'method', 'file_dir', 'file_path'\n"; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
elsif ( $attr_count > 1 ) |
350
|
|
|
|
|
|
|
{ |
351
|
0
|
|
|
|
|
0
|
croak "define_node '$path': you may only specify one of 'file_dir' and 'file_path'\n"; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
# Throw an error if any of the specified formats fails to match an |
355
|
|
|
|
|
|
|
# existing format. If any of the formats has a default vocabulary, add it |
356
|
|
|
|
|
|
|
# to the vocabulary list. |
357
|
|
|
|
|
|
|
|
358
|
2
|
|
|
|
|
6
|
my $allow_format = $ds->node_attr($path, 'allow_format'); |
359
|
|
|
|
|
|
|
|
360
|
2
|
50
|
33
|
|
|
13
|
if ( ref $allow_format && reftype $allow_format eq 'HASH' ) |
361
|
|
|
|
|
|
|
{ |
362
|
2
|
|
|
|
|
7
|
foreach my $f ( keys %$allow_format ) |
363
|
|
|
|
|
|
|
{ |
364
|
|
|
|
|
|
|
croak "define_node '$path': invalid value '$f' for format, no such format has been defined for this data service\n" |
365
|
4
|
50
|
|
|
|
10
|
unless ref $ds->{format}{$f}; |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
#my $dv = $ds->{format}{$f}{default_vocab}; |
368
|
|
|
|
|
|
|
#$node_attrs->{allow_vocab}{$dv} = 1 if $dv; |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
# Throw an error if any of the specified vocabularies fails to match an |
373
|
|
|
|
|
|
|
# existing vocabulary. |
374
|
|
|
|
|
|
|
|
375
|
2
|
|
|
|
|
6
|
my $allow_vocab = $ds->node_attr($path, 'allow_vocab'); |
376
|
|
|
|
|
|
|
|
377
|
2
|
50
|
33
|
|
|
10
|
if ( ref $allow_vocab && reftype $allow_vocab eq 'HASH' ) |
378
|
|
|
|
|
|
|
{ |
379
|
2
|
|
|
|
|
6
|
foreach my $v ( keys %$allow_vocab ) |
380
|
|
|
|
|
|
|
{ |
381
|
|
|
|
|
|
|
croak "define_node '$path': invalid value '$v' for vocab, no such vocabulary has been defined for this data service\n" |
382
|
4
|
50
|
|
|
|
12
|
unless ref $ds->{vocab}{$v}; |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
# Throw an error if 'place' is not greater than zero. |
387
|
|
|
|
|
|
|
|
388
|
2
|
|
|
|
|
6
|
my $place = $ds->node_attr($path, 'place'); |
389
|
|
|
|
|
|
|
|
390
|
2
|
|
|
2
|
|
17
|
no warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
3149
|
|
391
|
|
|
|
|
|
|
|
392
|
2
|
50
|
33
|
|
|
8
|
if ( defined $place && $place !~ qr{^[0-9]+$} ) |
393
|
|
|
|
|
|
|
{ |
394
|
0
|
|
|
|
|
0
|
croak "define_node '$path': the value of 'place' must be an integer"; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
2
|
|
|
|
|
4
|
my $a = 1; # we can stop here when debugging; |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
our (%LIST_DEF) = ( path => 'single', |
402
|
|
|
|
|
|
|
place => 'single', |
403
|
|
|
|
|
|
|
list => 'single', |
404
|
|
|
|
|
|
|
title => 'single', |
405
|
|
|
|
|
|
|
usage => 'single', |
406
|
|
|
|
|
|
|
doc_string => 'single' ); |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
# list_node ( attrs... ) |
409
|
|
|
|
|
|
|
# |
410
|
|
|
|
|
|
|
# Add an entry to a node list. |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub list_node { |
413
|
|
|
|
|
|
|
|
414
|
0
|
|
|
0
|
0
|
0
|
my $ds = shift; |
415
|
|
|
|
|
|
|
|
416
|
0
|
|
|
|
|
0
|
my ($last_node); |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# Now we go through the rest of the arguments. Hashrefs define new |
419
|
|
|
|
|
|
|
# list entries, while strings add to the documentation of the entry |
420
|
|
|
|
|
|
|
# whose definition they follow. |
421
|
|
|
|
|
|
|
|
422
|
0
|
|
|
|
|
0
|
foreach my $item (@_) |
423
|
|
|
|
|
|
|
{ |
424
|
|
|
|
|
|
|
# A hashref defines a new directory. |
425
|
|
|
|
|
|
|
|
426
|
0
|
0
|
|
|
|
0
|
if ( ref $item eq 'HASH' ) |
|
|
0
|
|
|
|
|
|
427
|
|
|
|
|
|
|
{ |
428
|
|
|
|
|
|
|
croak "list_node: each definition must include a non-empty value for 'path'\n" |
429
|
0
|
0
|
0
|
|
|
0
|
unless defined $item->{path} && $item->{path} ne ''; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
croak "list_node: invalid path '$item->{path}'\n" if $item->{path} ne '/' && |
432
|
0
|
0
|
0
|
|
|
0
|
$item->{path} =~ qr{ ^ / | / $ | // | [?#] }xs; |
433
|
|
|
|
|
|
|
|
434
|
0
|
|
|
|
|
0
|
$last_node = $ds->_create_list_entry($item); |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
elsif ( not ref $item ) |
438
|
|
|
|
|
|
|
{ |
439
|
0
|
|
|
|
|
0
|
$ds->add_node_doc($last_node, $item); |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
else |
443
|
|
|
|
|
|
|
{ |
444
|
0
|
|
|
|
|
0
|
croak "list_node: the arguments must be a list of hashrefs and strings\n"; |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
0
|
0
|
|
|
|
0
|
croak "list_node: arguments must include at least one hashref of attributes\n" |
449
|
|
|
|
|
|
|
unless $last_node; |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
sub _create_list_entry { |
454
|
|
|
|
|
|
|
|
455
|
0
|
|
|
0
|
|
0
|
my ($ds, $item) = @_; |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
# Start by checking the attributes. |
458
|
|
|
|
|
|
|
|
459
|
0
|
|
|
|
|
0
|
my $path = $item->{path}; |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
KEY: |
462
|
0
|
|
|
|
|
0
|
foreach my $key ( keys %$item ) |
463
|
|
|
|
|
|
|
{ |
464
|
|
|
|
|
|
|
croak "list_node '$path': unknown attribute '$key'\n" |
465
|
0
|
0
|
|
|
|
0
|
unless $NODE_DEF{$key}; |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
|
468
|
0
|
|
|
|
|
0
|
my $place = $item->{place}; |
469
|
0
|
|
|
|
|
0
|
my $list = $item->{list}; |
470
|
|
|
|
|
|
|
|
471
|
0
|
0
|
0
|
|
|
0
|
croak "list_node '$path': you must specify a numeric value for 'place'\n" |
472
|
|
|
|
|
|
|
unless defined $place && $place =~ qr{^[0-9]+$}; |
473
|
|
|
|
|
|
|
|
474
|
0
|
0
|
0
|
|
|
0
|
croak "list_node '$path': you must specify a non-empty value for 'list'\n" |
475
|
|
|
|
|
|
|
unless defined $list && $list ne ''; |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# Then install the item. |
478
|
|
|
|
|
|
|
|
479
|
0
|
0
|
|
|
|
0
|
push @{$ds->{node_list}{$list}{$place}}, $item if $place; |
|
0
|
|
|
|
|
0
|
|
480
|
|
|
|
|
|
|
|
481
|
0
|
|
|
|
|
0
|
return $item; |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# extended_doc ( attrs ... ) |
486
|
|
|
|
|
|
|
# |
487
|
|
|
|
|
|
|
# Add extended documentation to one or more nodes. The documentation strings |
488
|
|
|
|
|
|
|
# defined by this call will be used to extend the documentation provided in |
489
|
|
|
|
|
|
|
# the original node definitions. By default, this extended documentation will |
490
|
|
|
|
|
|
|
# be appended to the documentation string (if any) specified in the calls to |
491
|
|
|
|
|
|
|
# 'define_node', for display at the top of the documentation page for each |
492
|
|
|
|
|
|
|
# node. The original documentation strings will be used to document lists of |
493
|
|
|
|
|
|
|
# nodes. |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
sub extended_doc { |
496
|
|
|
|
|
|
|
|
497
|
0
|
|
|
0
|
0
|
0
|
my $ds = shift; |
498
|
|
|
|
|
|
|
|
499
|
0
|
|
|
|
|
0
|
my ($last_node); |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
# Now we go through the rest of the arguments. Hashrefs select or other |
502
|
|
|
|
|
|
|
# elements to be documented, while strings add to the documentation of the |
503
|
|
|
|
|
|
|
# selected element. |
504
|
|
|
|
|
|
|
|
505
|
0
|
|
|
|
|
0
|
foreach my $item (@_) |
506
|
|
|
|
|
|
|
{ |
507
|
|
|
|
|
|
|
# A hashref selects a node to be documented. |
508
|
|
|
|
|
|
|
|
509
|
0
|
0
|
|
|
|
0
|
if ( ref $item eq 'HASH' ) |
|
|
0
|
|
|
|
|
|
510
|
|
|
|
|
|
|
{ |
511
|
|
|
|
|
|
|
croak "extended_doc: each definition must include a non-empty value for either 'path' or 'type'\n" |
512
|
|
|
|
|
|
|
unless (defined $item->{path} && $item->{path} ne '' || |
513
|
0
|
0
|
0
|
|
|
0
|
defined $item->{type} && $item->{type} ne ''); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
croak "define_node: invalid path '$item->{path}'\n" if $item->{path} ne '/' && |
516
|
0
|
0
|
0
|
|
|
0
|
$item->{path} =~ qr{ ^ / | / $ | // | [?#] }xs; |
517
|
|
|
|
|
|
|
|
518
|
0
|
|
|
|
|
0
|
$last_node = $ds->_select_extended_doc($item); |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
elsif ( not ref $item ) |
522
|
|
|
|
|
|
|
{ |
523
|
0
|
|
|
|
|
0
|
$ds->_add_extended_doc($last_node, $item); |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
else |
527
|
|
|
|
|
|
|
{ |
528
|
0
|
|
|
|
|
0
|
croak "extended_doc: the arguments must be a list of hashrefs and strings\n"; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
0
|
0
|
|
|
|
0
|
croak "extended_doc: arguments must include at least one hashref of attributes\n" |
533
|
|
|
|
|
|
|
unless $last_node; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# _select_extended_doc ( attrs ) |
538
|
|
|
|
|
|
|
# |
539
|
|
|
|
|
|
|
# Return a reference to the extended documentation record corresponding to the |
540
|
|
|
|
|
|
|
# specified attributes. Create the record if it does not already exist. |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
sub _select_extended_doc { |
543
|
|
|
|
|
|
|
|
544
|
0
|
|
|
0
|
|
0
|
my ($ds, $item) = @_; |
545
|
|
|
|
|
|
|
|
546
|
0
|
|
0
|
|
|
0
|
my $disp = $item->{disp} || ''; |
547
|
0
|
|
0
|
|
|
0
|
my $type = $item->{type} || 'node'; |
548
|
0
|
|
|
|
|
0
|
my $path = $item->{path}; |
549
|
0
|
|
0
|
|
|
0
|
my $name = $path || $item->{name}; |
550
|
|
|
|
|
|
|
|
551
|
0
|
0
|
|
|
|
0
|
croak "extended_doc: you must specify either 'name' or 'path' in each set of attributes\n" |
552
|
|
|
|
|
|
|
unless $name; |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
KEY: |
555
|
0
|
|
|
|
|
0
|
foreach my $key ( keys %$item ) |
556
|
|
|
|
|
|
|
{ |
557
|
|
|
|
|
|
|
croak "extended_doc '$name': unknown attribute '$key'\n" |
558
|
0
|
0
|
|
|
|
0
|
unless $EXTENDED_DEF{$key}; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
0
|
0
|
0
|
|
|
0
|
croak "extended_doc '$name': value of disp must be either 'replace', 'add' or 'para'\n" |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
562
|
|
|
|
|
|
|
unless $disp eq '' || $disp eq 'replace' || $disp eq 'add' || $disp eq 'para'; |
563
|
|
|
|
|
|
|
|
564
|
0
|
0
|
|
|
|
0
|
if ( $path ) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
565
|
|
|
|
|
|
|
{ |
566
|
|
|
|
|
|
|
croak "extended_doc '$path': you may not specify both 'path' and 'name'\n" |
567
|
0
|
0
|
|
|
|
0
|
if $item->{name}; |
568
|
|
|
|
|
|
|
|
569
|
0
|
0
|
|
|
|
0
|
croak "extended_doc '$path': type must be 'node' if you also specify 'path'\n" |
570
|
|
|
|
|
|
|
if $type ne 'node'; |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
croak "extended_node '$path': no such node has been defined\n" |
573
|
0
|
0
|
|
|
|
0
|
unless ref $ds->{node_attrs}{$path} eq 'HASH'; |
574
|
|
|
|
|
|
|
|
575
|
0
|
|
0
|
|
|
0
|
$ds->{extdoc_node}{$path} ||= { path => $path, disp => 'para', type => 'node' }; |
576
|
0
|
0
|
|
|
|
0
|
$ds->{extdoc_node}{$path}{disp} = $disp if $disp; |
577
|
0
|
|
|
|
|
0
|
return $ds->{extdoc_node}{$path}; |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
elsif ( $type eq 'format' ) |
581
|
|
|
|
|
|
|
{ |
582
|
0
|
0
|
|
|
|
0
|
croak "extended_doc: you must specify either a path or a name for every record\n" |
583
|
|
|
|
|
|
|
unless $name; |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
croak "extended_doc '$name': no such format has been defined\n" |
586
|
0
|
0
|
|
|
|
0
|
unless ref $ds->{format}{$name} eq 'Web::DataService::Format'; |
587
|
|
|
|
|
|
|
|
588
|
0
|
|
0
|
|
|
0
|
$ds->{extdoc_format}{$name} ||= { name => $name, disp => 'para', type => 'format' }; |
589
|
0
|
0
|
|
|
|
0
|
$ds->{extdoc_format}{$name}{disp} = $disp if $disp; |
590
|
0
|
|
|
|
|
0
|
return $ds->{extdoc_format}{$name}; |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
elsif ( $type eq 'vocab' ) |
594
|
|
|
|
|
|
|
{ |
595
|
0
|
0
|
|
|
|
0
|
croak "extended_doc: you must specify either a path or a name for every record\n" |
596
|
|
|
|
|
|
|
unless $name; |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
croak "extended_doc '$name': no such vocabulary has been defined\n" |
599
|
0
|
0
|
|
|
|
0
|
unless ref $ds->{format}{$name} eq 'Web::DataService::Vocab'; |
600
|
|
|
|
|
|
|
|
601
|
0
|
|
0
|
|
|
0
|
$ds->{extdoc_vocab}{$name} ||= { name => $name, disp => $disp, type => 'vocab' }; |
602
|
0
|
0
|
|
|
|
0
|
$ds->{extdoc_vocab}{$name}{disp} = $disp if $disp; |
603
|
0
|
|
|
|
|
0
|
return $ds->{extdoc_vocab}{$name}; |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
else |
607
|
|
|
|
|
|
|
{ |
608
|
0
|
0
|
|
|
|
0
|
croak "extended_doc '$name': you must specify an element type, i.e. 'vocab' or 'format'\n" |
609
|
|
|
|
|
|
|
unless $type; |
610
|
|
|
|
|
|
|
|
611
|
0
|
0
|
|
|
|
0
|
croak "extended_doc '$type': you must specify a node path\n" |
612
|
|
|
|
|
|
|
if $type eq 'node'; |
613
|
|
|
|
|
|
|
|
614
|
0
|
0
|
0
|
|
|
0
|
croak "extended_doc '$name': invalid type '$type', must be either 'node', 'format' or 'vocab'\n" |
|
|
|
0
|
|
|
|
|
615
|
|
|
|
|
|
|
unless $type eq 'node' || $type eq 'format' || $type eq 'vocab'; |
616
|
|
|
|
|
|
|
|
617
|
0
|
|
|
|
|
0
|
croak "extended_doc '$name': invalid attributes"; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
sub _add_extended_doc { |
623
|
|
|
|
|
|
|
|
624
|
0
|
|
|
0
|
|
0
|
my ($ds, $item, $doc) = @_; |
625
|
|
|
|
|
|
|
|
626
|
0
|
0
|
|
|
|
0
|
return unless defined $doc; |
627
|
|
|
|
|
|
|
|
628
|
0
|
|
0
|
|
|
0
|
my $name = $item->{path} || $item->{name}; |
629
|
|
|
|
|
|
|
|
630
|
0
|
0
|
|
|
|
0
|
croak "extended_doc '$name': only strings may be added to documentation: $doc is not valid" |
631
|
|
|
|
|
|
|
if ref $doc; |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
# If the string starts with either '>' or '>>', add an extra blank line so |
634
|
|
|
|
|
|
|
# that it becomes a new paragraph. We ignore an initial '!'. If you wish |
635
|
|
|
|
|
|
|
# to mark a node as undocumented, do so in the 'define_node' call. |
636
|
|
|
|
|
|
|
|
637
|
0
|
|
|
|
|
0
|
$doc =~ s{^>>?}{\n}xs; |
638
|
0
|
|
|
|
|
0
|
$doc =~ s{^[!]}{}xs; |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
# Now add the documentation string. |
641
|
|
|
|
|
|
|
|
642
|
0
|
0
|
|
|
|
0
|
$item->{doc_string} = '' unless defined $item->{doc_string}; |
643
|
0
|
0
|
|
|
|
0
|
$item->{doc_string} .= "\n" if $item->{doc_string} ne ''; |
644
|
0
|
|
|
|
|
0
|
$item->{doc_string} .= $doc; |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
# node_defined ( path ) |
649
|
|
|
|
|
|
|
# |
650
|
|
|
|
|
|
|
# Return true if the specified path has been defined, false otherwise. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub node_defined { |
653
|
|
|
|
|
|
|
|
654
|
0
|
|
|
0
|
0
|
0
|
my ($ds, $path) = @_; |
655
|
|
|
|
|
|
|
|
656
|
0
|
0
|
|
|
|
0
|
return unless defined $path; |
657
|
0
|
0
|
|
|
|
0
|
$path = '/' if $path eq ''; |
658
|
|
|
|
|
|
|
|
659
|
0
|
|
0
|
|
|
0
|
return $ds->{node_attrs}{$path} && ! $ds->{node_attrs}{$path}{disabled}; |
660
|
|
|
|
|
|
|
} |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
# node_attr ( path, key ) |
664
|
|
|
|
|
|
|
# |
665
|
|
|
|
|
|
|
# Return the specified attribute for the given path. These are computed |
666
|
|
|
|
|
|
|
# lazily; if the specified attribute is already in the attribute cache, then |
667
|
|
|
|
|
|
|
# return it. Otherwise, we must look it up. |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
sub node_attr { |
670
|
|
|
|
|
|
|
|
671
|
19
|
|
|
19
|
0
|
3290
|
my ($ds, $path, $key) = @_; |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
# If we are given an object as the value of $path, pull out its |
674
|
|
|
|
|
|
|
# 'node_path' attribute, or else default to the root path '/'. |
675
|
|
|
|
|
|
|
|
676
|
19
|
50
|
33
|
|
|
43
|
if ( ref $path && reftype $path eq 'HASH' ) |
677
|
|
|
|
|
|
|
{ |
678
|
0
|
|
0
|
|
|
0
|
$path = $path->{node_path} || '/'; |
679
|
|
|
|
|
|
|
} |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# If the specified attribute is in the attribute cache for this path, just |
682
|
|
|
|
|
|
|
# return it. Even if the value is undefined. We need to turn off warnings |
683
|
|
|
|
|
|
|
# for this block, because either of $path or $key may be undefined. The |
684
|
|
|
|
|
|
|
# behavior is correct in any case, we just don't want the warning. |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
{ |
687
|
2
|
|
|
2
|
|
19
|
no warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
2465
|
|
|
19
|
|
|
|
|
20
|
|
688
|
19
|
100
|
|
|
|
39
|
if ( exists $ds->{attr_cache}{$path}{$key} ) |
689
|
|
|
|
|
|
|
{ |
690
|
2
|
|
|
|
|
11
|
return $ds->{attr_cache}{$path}{$key}; |
691
|
|
|
|
|
|
|
#return ref $ds->{attr_cache}{$path}{$key} eq 'ARRAY' ? |
692
|
|
|
|
|
|
|
# @{$ds->{attr_cache}{$path}{$key}} : $ds->{attr_cache}{$path}{$key}; |
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
# If no key is given, or an invalid key is given, then return undefined. |
697
|
|
|
|
|
|
|
# If no path is given, return undefined. If the empty string is given for |
698
|
|
|
|
|
|
|
# the path, return the root attribute. |
699
|
|
|
|
|
|
|
|
700
|
17
|
50
|
33
|
|
|
60
|
return unless $key && defined $NODE_DEF{$key}; |
701
|
17
|
50
|
33
|
|
|
44
|
return unless defined $path && $path ne ''; |
702
|
|
|
|
|
|
|
|
703
|
17
|
50
|
|
|
|
26
|
$path = '/' if $path eq ''; |
704
|
|
|
|
|
|
|
|
705
|
17
|
50
|
|
|
|
35
|
return unless exists $ds->{node_attrs}{$path}; |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# Otherwise, look up what the value should be and store it in the cache. |
708
|
|
|
|
|
|
|
|
709
|
17
|
|
|
|
|
30
|
return $ds->_lookup_node_attr($path, $key); |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
# _lookup_node_attr ( path, key ) |
714
|
|
|
|
|
|
|
# |
715
|
|
|
|
|
|
|
# Look up the specified attribute for the given path. If it is not defined |
716
|
|
|
|
|
|
|
# for the specified path, look for a parent path. If it is not defined for |
717
|
|
|
|
|
|
|
# any of the parents, see if the data service has the specified attribute. |
718
|
|
|
|
|
|
|
# Because this is an internal routine, we skip the 'defined' checks. |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
sub _lookup_node_attr { |
721
|
|
|
|
|
|
|
|
722
|
24
|
|
|
24
|
|
34
|
my ($ds, $path, $key) = @_; |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# First create an attribute cache for this path if one does not already exist. |
725
|
|
|
|
|
|
|
|
726
|
24
|
|
50
|
|
|
46
|
$ds->{attr_cache}{$path} //= {}; |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
# If the attribute is non-heritable, then just cache and return whatever |
729
|
|
|
|
|
|
|
# is defined for this node. |
730
|
|
|
|
|
|
|
|
731
|
24
|
100
|
|
|
|
41
|
if ( $NODE_NONHERITABLE{$key} ) |
732
|
|
|
|
|
|
|
{ |
733
|
3
|
|
|
|
|
15
|
return $ds->{attr_cache}{$path}{$key} = $ds->{node_attrs}{$path}{$key}; |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
# Otherwise check if the path actually has a value for this attribute. |
737
|
|
|
|
|
|
|
# If it does not, or if the corresponding path_compose entry is set, then |
738
|
|
|
|
|
|
|
# look up the value for the parent node if there is one. |
739
|
|
|
|
|
|
|
|
740
|
21
|
|
|
|
|
25
|
my $inherited_value; |
741
|
|
|
|
|
|
|
|
742
|
21
|
100
|
66
|
|
|
47
|
if ( ! exists $ds->{node_attrs}{$path}{$key} || $ds->{path_compose}{$path}{$key} ) |
743
|
|
|
|
|
|
|
{ |
744
|
19
|
|
|
|
|
33
|
my $parent = $ds->path_parent($path); |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
# If we have a parent, look up the attribute there and put the value |
747
|
|
|
|
|
|
|
# in the cache for the current path. |
748
|
|
|
|
|
|
|
|
749
|
19
|
100
|
|
|
|
30
|
if ( defined $parent ) |
750
|
|
|
|
|
|
|
{ |
751
|
7
|
|
|
|
|
16
|
$inherited_value = $ds->_lookup_node_attr($parent, $key); |
752
|
|
|
|
|
|
|
} |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# Otherwise, if the attribute is defined in the configuration file |
755
|
|
|
|
|
|
|
# then look it up there. |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
else |
758
|
|
|
|
|
|
|
{ |
759
|
12
|
|
|
|
|
34
|
my $config_value = $ds->config_value($key); |
760
|
|
|
|
|
|
|
|
761
|
12
|
50
|
|
|
|
42
|
if ( defined $config_value ) |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
762
|
|
|
|
|
|
|
{ |
763
|
0
|
|
|
|
|
0
|
$inherited_value = $config_value; |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
# If it is not defined in the configuration file, see if we have a |
767
|
|
|
|
|
|
|
# universal default. |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
elsif ( defined $NODE_ATTR_DEFAULT{$key} ) |
770
|
|
|
|
|
|
|
{ |
771
|
0
|
|
|
|
|
0
|
$inherited_value = $NODE_ATTR_DEFAULT{$key}; |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
# Otherwise, if this is one of the following attributes, use the |
775
|
|
|
|
|
|
|
# indicated default. |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
elsif ( $key eq 'allow_method' ) |
778
|
|
|
|
|
|
|
{ |
779
|
0
|
|
|
|
|
0
|
my %default_methods = map { $_ => 1 } @Web::DataService::DEFAULT_METHODS; |
|
0
|
|
|
|
|
0
|
|
780
|
0
|
|
|
|
|
0
|
$inherited_value = \%default_methods; |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
elsif ( $key eq 'allow_format' ) |
784
|
|
|
|
|
|
|
{ |
785
|
2
|
|
|
|
|
4
|
my %default_formats = map { $_ => 1 } @{$ds->{format_list}}; |
|
4
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
7
|
|
786
|
2
|
|
|
|
|
14
|
$inherited_value = \%default_formats; |
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
elsif ( $key eq 'allow_vocab' ) |
790
|
|
|
|
|
|
|
{ |
791
|
2
|
|
|
|
|
3
|
my %default_vocab = map { $_ => 1 } @{$ds->{vocab_list}}; |
|
4
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
6
|
|
792
|
2
|
|
|
|
|
6
|
$inherited_value = \%default_vocab; |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
# If no value exists for the current path, cache and return the value we |
797
|
|
|
|
|
|
|
# just looked up. Or undef if we didn't find any value. |
798
|
|
|
|
|
|
|
|
799
|
19
|
50
|
|
|
|
41
|
if ( ! exists $ds->{node_attrs}{$path}{$key} ) |
800
|
|
|
|
|
|
|
{ |
801
|
19
|
|
|
|
|
31
|
$ds->{attr_cache}{$path}{$key} = $inherited_value; |
802
|
19
|
|
|
|
|
43
|
return $ds->{attr_cache}{$path}{$key}; |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
} |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# If we get here then we need to compose the inherited value with the |
807
|
|
|
|
|
|
|
# value from the current node. |
808
|
|
|
|
|
|
|
|
809
|
2
|
|
|
|
|
4
|
my $new_value; |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# If the attribute type is 'set', then separate the value by commas. If |
812
|
|
|
|
|
|
|
# we have an inherited value, start with it and add or delete sub-values |
813
|
|
|
|
|
|
|
# as indicated. |
814
|
|
|
|
|
|
|
|
815
|
2
|
50
|
|
|
|
15
|
if ( $NODE_DEF{$key} eq 'set' ) |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
816
|
|
|
|
|
|
|
{ |
817
|
0
|
0
|
|
|
|
0
|
$new_value = ref $inherited_value eq 'HASH' ? { %$inherited_value } : { }; |
818
|
0
|
|
0
|
|
|
0
|
my $string_value = $ds->{node_attrs}{$path}{$key} // ''; |
819
|
|
|
|
|
|
|
|
820
|
0
|
|
|
|
|
0
|
foreach my $v ( split( /\s*,\s*/, $string_value ) ) |
821
|
|
|
|
|
|
|
{ |
822
|
0
|
0
|
|
|
|
0
|
next unless $v =~ /^([+-])?(.*)/; |
823
|
|
|
|
|
|
|
|
824
|
0
|
0
|
0
|
|
|
0
|
if ( defined $1 && $1 eq '-' ) |
825
|
|
|
|
|
|
|
{ |
826
|
0
|
|
|
|
|
0
|
delete $new_value->{$2}; |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
else |
830
|
|
|
|
|
|
|
{ |
831
|
0
|
|
|
|
|
0
|
$new_value->{$2} = 1; |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
} |
834
|
|
|
|
|
|
|
} |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
# If the attribute type is 'list', then separate the value by commas and |
837
|
|
|
|
|
|
|
# create a list. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
elsif ( $NODE_DEF{$key} eq 'list' ) |
840
|
|
|
|
|
|
|
{ |
841
|
1
|
|
|
|
|
2
|
$new_value = [ ]; |
842
|
1
|
|
50
|
|
|
6
|
my $string_value = $ds->{node_attrs}{$path}{$key} // ''; |
843
|
|
|
|
|
|
|
|
844
|
1
|
|
|
|
|
8
|
foreach my $v ( split( /\s*,\s*/, $string_value ) ) |
845
|
|
|
|
|
|
|
{ |
846
|
1
|
50
|
33
|
|
|
8
|
push @$new_value, $v if defined $v && $v ne ''; |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
# If the attribute type is 'hook', then add the new value to the end of the previous list. |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
elsif ( $NODE_DEF{$key} eq 'hook' ) |
853
|
|
|
|
|
|
|
{ |
854
|
0
|
0
|
0
|
|
|
0
|
if ( ref $inherited_value eq 'ARRAY' && @$inherited_value ) |
855
|
|
|
|
|
|
|
{ |
856
|
0
|
|
|
|
|
0
|
$new_value = [ @$inherited_value, @{$ds->{node_attrs}{$path}{$key}} ]; |
|
0
|
|
|
|
|
0
|
|
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
else |
860
|
|
|
|
|
|
|
{ |
861
|
0
|
|
|
|
|
0
|
$new_value = $ds->{node_attrs}{$path}{$key}; |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
} |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# Otherwise, the new value simply overrides any inherited value. This code |
866
|
|
|
|
|
|
|
# path is only here in case path_compose is set mistakenly for some attribute |
867
|
|
|
|
|
|
|
# of type 'single'. |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
else |
870
|
|
|
|
|
|
|
{ |
871
|
1
|
|
|
|
|
3
|
$new_value = $ds->{node_attrs}{$path}{$key}; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
# Stuff the new value into the cache and return it. |
875
|
|
|
|
|
|
|
|
876
|
2
|
|
|
|
|
13
|
return $ds->{attr_cache}{$path}{$key} = $new_value; |
877
|
|
|
|
|
|
|
} |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
# path_parent ( path ) |
881
|
|
|
|
|
|
|
# |
882
|
|
|
|
|
|
|
# Return the parent path of the given path. For example, the parent of "a/b" |
883
|
|
|
|
|
|
|
# is "a". The parent of "a" is "/". The parent of "/" or is undefined. So |
884
|
|
|
|
|
|
|
# is the parent of "", though that is not a valid path. |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
sub path_parent { |
887
|
|
|
|
|
|
|
|
888
|
19
|
|
|
19
|
0
|
22
|
my ($ds, $path) = @_; |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
# If $path is defined, we cache the lookup values undef 'path_parent'. |
891
|
|
|
|
|
|
|
|
892
|
19
|
50
|
|
|
|
27
|
return undef unless defined $path; |
893
|
19
|
100
|
|
|
|
47
|
return $ds->{path_parent}{$path} if exists $ds->{path_parent}{$path}; |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
# If not found, add it to the cache and return it. |
896
|
|
|
|
|
|
|
|
897
|
2
|
100
|
66
|
|
|
14
|
if ( $path eq '/' || $path eq '' ) |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
898
|
|
|
|
|
|
|
{ |
899
|
1
|
|
|
|
|
4
|
return $ds->{path_parent}{$path} = undef; |
900
|
|
|
|
|
|
|
} |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
elsif ( $path =~ qr{ ^ [^/]+ $ }xs ) |
903
|
|
|
|
|
|
|
{ |
904
|
1
|
|
|
|
|
5
|
return $ds->{path_parent}{$path} = '/'; |
905
|
|
|
|
|
|
|
} |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
elsif ( $path =~ qr{ ^ (.+) / [^/]+ }xs ) |
908
|
|
|
|
|
|
|
{ |
909
|
0
|
|
|
|
|
0
|
return $ds->{path_parent}{$path} = $1; |
910
|
|
|
|
|
|
|
} |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
else |
913
|
|
|
|
|
|
|
{ |
914
|
0
|
|
|
|
|
0
|
return $ds->{path_parent}{$path} = undef; |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
# add_node_doc ( node, doc_string ) |
920
|
|
|
|
|
|
|
# |
921
|
|
|
|
|
|
|
# Add the specified documentation string to the specified node. |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
sub add_node_doc { |
924
|
|
|
|
|
|
|
|
925
|
3
|
|
|
3
|
0
|
11
|
my ($ds, $node, $doc) = @_; |
926
|
|
|
|
|
|
|
|
927
|
3
|
50
|
|
|
|
7
|
return unless defined $doc; |
928
|
|
|
|
|
|
|
|
929
|
3
|
50
|
|
|
|
11
|
croak "only strings may be added to documentation: '$doc' is not valid" |
930
|
|
|
|
|
|
|
if ref $doc; |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
# If the first documentation string starts with !, mark the node as |
933
|
|
|
|
|
|
|
# undocumented and remove the '!'. |
934
|
|
|
|
|
|
|
|
935
|
3
|
50
|
|
|
|
9
|
unless ( $node->{doc_string} ) |
936
|
|
|
|
|
|
|
{ |
937
|
3
|
50
|
|
|
|
32
|
if ( $doc =~ qr{ ^ ! (.*) }xs ) |
938
|
|
|
|
|
|
|
{ |
939
|
0
|
|
|
|
|
0
|
$doc = $1; |
940
|
0
|
|
|
|
|
0
|
$node->{undocumented} = 1; |
941
|
|
|
|
|
|
|
} |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
# Change any initial > or >> into a blank line, to indicate a new |
945
|
|
|
|
|
|
|
# paragraph. |
946
|
|
|
|
|
|
|
|
947
|
3
|
|
|
|
|
10
|
$doc =~ s{^>>?}{\n}xs; |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
# Now add the documentation string. |
950
|
|
|
|
|
|
|
|
951
|
3
|
50
|
|
|
|
12
|
$node->{doc_string} = '' unless defined $node->{doc_string}; |
952
|
3
|
50
|
33
|
|
|
11
|
$node->{doc_string} .= "\n" if $node->{doc_string} ne '' && $doc ne ''; |
953
|
3
|
|
|
|
|
12
|
$node->{doc_string} .= $doc; |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
1; |