| 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
|
|
15
|
use strict; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
88
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Web::DataService::Node; |
|
12
|
|
|
|
|
|
|
|
|
13
|
2
|
|
|
2
|
|
13
|
use Carp 'croak'; |
|
|
2
|
|
|
|
|
11
|
|
|
|
2
|
|
|
|
|
104
|
|
|
14
|
2
|
|
|
2
|
|
14
|
use Scalar::Util 'reftype'; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
85
|
|
|
15
|
|
|
|
|
|
|
|
|
16
|
2
|
|
|
2
|
|
991
|
use Moo::Role; |
|
|
2
|
|
|
|
|
28479
|
|
|
|
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
|
|
|
|
|
|
|
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
|
11
|
my $ds = shift; |
|
97
|
|
|
|
|
|
|
|
|
98
|
1
|
|
|
|
|
4
|
my ($package, $filename, $line) = caller; |
|
99
|
|
|
|
|
|
|
|
|
100
|
1
|
|
|
|
|
2
|
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
|
|
|
|
8
|
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
|
|
|
11
|
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
|
|
|
25
|
$item->{path} =~ qr{ ^ / | / $ | // | [?#] }xs; |
|
117
|
|
|
|
|
|
|
|
|
118
|
2
|
|
|
|
|
8
|
$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
|
|
|
|
4
|
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
|
|
7
|
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
|
|
|
|
8
|
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
|
|
|
|
|
6
|
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
|
|
|
|
|
8
|
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
|
|
|
|
|
12
|
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
|
|
|
|
|
7
|
$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
|
|
|
|
12
|
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
|
|
|
|
|
5
|
my $place = $node_attrs->{place}; |
|
264
|
|
|
|
|
|
|
|
|
265
|
2
|
50
|
|
|
|
5
|
if ( defined $place ) |
|
266
|
|
|
|
|
|
|
{ |
|
267
|
0
|
|
0
|
|
|
0
|
my $list = $node_attrs->{list} // $ds->path_parent($path); |
|
268
|
|
|
|
|
|
|
|
|
269
|
2
|
|
|
2
|
|
3142
|
no warnings; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
504
|
|
|
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
|
|
|
|
|
8
|
$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
|
|
|
|
|
13
|
my $role = $ds->node_attr($path, 'role'); |
|
289
|
|
|
|
|
|
|
|
|
290
|
2
|
50
|
33
|
|
|
7
|
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
|
|
|
|
|
6
|
return $node_attrs; |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
sub _check_path_node { |
|
303
|
|
|
|
|
|
|
|
|
304
|
2
|
|
|
2
|
|
5
|
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
|
|
|
|
6
|
if ( $role ) |
|
311
|
|
|
|
|
|
|
{ |
|
312
|
2
|
|
|
2
|
|
30
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
794
|
|
|
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
|
|
|
|
|
11
|
my $attr_count = 0; |
|
339
|
|
|
|
|
|
|
|
|
340
|
2
|
50
|
|
|
|
8
|
$attr_count++ if $method; |
|
341
|
2
|
50
|
|
|
|
5
|
$attr_count++ if $ds->node_attr($path, 'file_dir'); |
|
342
|
2
|
50
|
|
|
|
5
|
$attr_count++ if $ds->node_attr($path, 'file_path'); |
|
343
|
|
|
|
|
|
|
|
|
344
|
2
|
50
|
33
|
|
|
14
|
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
|
|
|
|
|
5
|
my $allow_format = $ds->node_attr($path, 'allow_format'); |
|
359
|
|
|
|
|
|
|
|
|
360
|
2
|
50
|
33
|
|
|
15
|
if ( ref $allow_format && reftype $allow_format eq 'HASH' ) |
|
361
|
|
|
|
|
|
|
{ |
|
362
|
2
|
|
|
|
|
15
|
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
|
|
|
|
14
|
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
|
|
|
12
|
if ( ref $allow_vocab && reftype $allow_vocab eq 'HASH' ) |
|
378
|
|
|
|
|
|
|
{ |
|
379
|
2
|
|
|
|
|
7
|
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
|
|
|
|
|
3489
|
|
|
391
|
|
|
|
|
|
|
|
|
392
|
2
|
50
|
33
|
|
|
6
|
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
|
|
|
|
|
5
|
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
|
3645
|
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
|
|
|
41
|
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
|
|
18
|
no warnings; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
2348
|
|
|
|
19
|
|
|
|
|
24
|
|
|
688
|
19
|
100
|
|
|
|
45
|
if ( exists $ds->{attr_cache}{$path}{$key} ) |
|
689
|
|
|
|
|
|
|
{ |
|
690
|
2
|
|
|
|
|
5
|
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
|
|
|
62
|
return unless $key && defined $NODE_DEF{$key}; |
|
701
|
17
|
50
|
33
|
|
|
52
|
return unless defined $path && $path ne ''; |
|
702
|
|
|
|
|
|
|
|
|
703
|
17
|
50
|
|
|
|
29
|
$path = '/' if $path eq ''; |
|
704
|
|
|
|
|
|
|
|
|
705
|
17
|
50
|
|
|
|
34
|
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
|
|
|
|
|
37
|
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
|
|
77
|
my ($ds, $path, $key) = @_; |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# First create an attribute cache for this path if one does not already exist. |
|
725
|
|
|
|
|
|
|
|
|
726
|
24
|
|
50
|
|
|
53
|
$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
|
|
|
|
49
|
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
|
|
|
|
|
26
|
my $inherited_value; |
|
741
|
|
|
|
|
|
|
|
|
742
|
21
|
100
|
66
|
|
|
57
|
if ( ! exists $ds->{node_attrs}{$path}{$key} || $ds->{path_compose}{$path}{$key} ) |
|
743
|
|
|
|
|
|
|
{ |
|
744
|
19
|
|
|
|
|
34
|
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
|
|
|
|
37
|
if ( defined $parent ) |
|
750
|
|
|
|
|
|
|
{ |
|
751
|
7
|
|
|
|
|
17
|
$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
|
|
|
|
|
32
|
my $config_value = $ds->config_value($key); |
|
760
|
|
|
|
|
|
|
|
|
761
|
12
|
50
|
|
|
|
46
|
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
|
|
|
|
|
5
|
my %default_formats = map { $_ => 1 } @{$ds->{format_list}}; |
|
|
4
|
|
|
|
|
13
|
|
|
|
2
|
|
|
|
|
5
|
|
|
786
|
2
|
|
|
|
|
5
|
$inherited_value = \%default_formats; |
|
787
|
|
|
|
|
|
|
} |
|
788
|
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
elsif ( $key eq 'allow_vocab' ) |
|
790
|
|
|
|
|
|
|
{ |
|
791
|
2
|
|
|
|
|
4
|
my %default_vocab = map { $_ => 1 } @{$ds->{vocab_list}}; |
|
|
4
|
|
|
|
|
10
|
|
|
|
2
|
|
|
|
|
5
|
|
|
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
|
|
|
|
|
37
|
$ds->{attr_cache}{$path}{$key} = $inherited_value; |
|
802
|
19
|
|
|
|
|
50
|
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
|
|
|
|
|
3
|
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
|
|
|
|
11
|
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
|
|
|
5
|
my $string_value = $ds->{node_attrs}{$path}{$key} // ''; |
|
843
|
|
|
|
|
|
|
|
|
844
|
1
|
|
|
|
|
5
|
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
|
|
|
|
|
12
|
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
|
27
|
my ($ds, $path) = @_; |
|
889
|
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
# If $path is defined, we cache the lookup values undef 'path_parent'. |
|
891
|
|
|
|
|
|
|
|
|
892
|
19
|
50
|
|
|
|
33
|
return undef unless defined $path; |
|
893
|
19
|
100
|
|
|
|
46
|
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
|
|
|
16
|
if ( $path eq '/' || $path eq '' ) |
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
{ |
|
899
|
1
|
|
|
|
|
5
|
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
|
7
|
my ($ds, $node, $doc) = @_; |
|
926
|
|
|
|
|
|
|
|
|
927
|
3
|
50
|
|
|
|
8
|
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
|
|
|
|
10
|
unless ( $node->{doc_string} ) |
|
936
|
|
|
|
|
|
|
{ |
|
937
|
3
|
50
|
|
|
|
22
|
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
|
|
|
|
|
8
|
$doc =~ s{^>>?}{\n}xs; |
|
948
|
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
# Now add the documentation string. |
|
950
|
|
|
|
|
|
|
|
|
951
|
3
|
50
|
|
|
|
11
|
$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
|
|
|
|
|
19
|
$node->{doc_string} .= $doc; |
|
954
|
|
|
|
|
|
|
} |
|
955
|
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
1; |