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