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