| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Mojolicious::Plugin::Util::Endpoint; | 
| 2 | 2 |  |  | 2 |  | 14884 | use Mojo::Base 'Mojolicious::Plugin'; | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 15 |  | 
| 3 | 2 |  |  | 2 |  | 428 | use Mojo::ByteStream 'b'; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 127 |  | 
| 4 | 2 |  |  | 2 |  | 12 | use Scalar::Util qw/blessed/; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 107 |  | 
| 5 | 2 |  |  | 2 |  | 14 | use Mojo::URL; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 18 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | our $VERSION = '0.24'; | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | # Todo: Support alternative bases for https-paths | 
| 10 |  |  |  |  |  |  | # Todo: Update to https://tools.ietf.org/html/rfc6570 | 
| 11 |  |  |  |  |  |  | # Todo: Allow for changing scheme, port, host etc. afterwards | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | # Endpoint hash | 
| 14 |  |  |  |  |  |  | our %endpoints; | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | # Register Plugin | 
| 17 |  |  |  |  |  |  | sub register { | 
| 18 | 2 |  |  | 2 | 1 | 129 | my ($plugin, $mojo) = @_; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | # Add 'endpoints' command | 
| 21 | 2 |  |  |  |  | 6 | push @{$mojo->commands->namespaces}, __PACKAGE__; | 
|  | 2 |  |  |  |  | 22 |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | # Add 'endpoint' shortcut | 
| 24 |  |  |  |  |  |  | $mojo->routes->add_shortcut( | 
| 25 |  |  |  |  |  |  | endpoint => sub { | 
| 26 | 14 |  |  | 14 |  | 8238 | my ($route, $name, $param) = @_; | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # Endpoint already defined | 
| 29 | 14 | 100 |  |  |  | 69 | if (exists $endpoints{$name}) { | 
| 30 | 1 |  |  |  |  | 5 | $mojo->log->debug(qq{Route endpoint "$name" already defined}); | 
| 31 | 1 |  |  |  |  | 839 | return $route; | 
| 32 |  |  |  |  |  |  | }; | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 13 |  | 100 |  |  | 66 | $param //= {}; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | # Route defined | 
| 37 | 13 |  |  |  |  | 55 | $param->{route} = $route->name; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | # Set to stash | 
| 40 | 13 |  | 50 |  |  | 154 | $endpoints{$name} = $param // {}; | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | # Return route for piping | 
| 43 | 13 |  |  |  |  | 40 | return $route; | 
| 44 |  |  |  |  |  |  | } | 
| 45 | 2 |  |  |  |  | 241 | ); | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | # Add 'endpoint' helper | 
| 49 |  |  |  |  |  |  | $mojo->helper( | 
| 50 |  |  |  |  |  |  | endpoint => sub { | 
| 51 | 59 |  |  | 59 |  | 66990 | my ($c, $name, $values) = @_; | 
| 52 | 59 |  | 100 |  |  | 299 | $values ||= {}; | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | # Define endpoint by string | 
| 55 | 59 | 100 | 100 |  |  | 398 | unless (ref $values) { | 
| 56 | 4 |  |  |  |  | 65 | return ($endpoints{$name} = Mojo::URL->new($values)); | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | # Define endpoint by Mojo::URL | 
| 60 |  |  |  |  |  |  | elsif (blessed $values && $values->isa('Mojo::URL')) { | 
| 61 |  |  |  |  |  |  | return ($endpoints{$name} = $values->clone); | 
| 62 |  |  |  |  |  |  | }; | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | # Set values | 
| 65 |  |  |  |  |  |  | my %values = ( | 
| 66 | 54 | 50 |  |  |  | 292 | $c->isa('Mojolicious::Controller') ? %{$c->stash} : %{$c->defaults}, | 
|  | 54 |  |  |  |  | 234 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 67 |  |  |  |  |  |  | format => undef, | 
| 68 |  |  |  |  |  |  | %$values | 
| 69 |  |  |  |  |  |  | ); | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | # Endpoint undefined | 
| 72 | 54 | 100 |  |  |  | 761 | unless (defined $endpoints{$name}) { | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | # Named route | 
| 75 | 7 | 100 |  |  |  | 52 | if ($name !~ m!^([^:]+:)?/?/!) { | 
| 76 | 1 |  |  |  |  | 29 | return $c->url_for($name)->to_abs->to_string; | 
| 77 |  |  |  |  |  |  | }; | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | # Interpolate string | 
| 80 | 6 |  |  |  |  | 22 | return _interpolate($name, \%values, $values); | 
| 81 |  |  |  |  |  |  | }; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # Return interpolated string | 
| 84 | 47 | 100 | 66 |  |  | 268 | if (blessed $endpoints{$name} && $endpoints{$name}->isa('Mojo::URL')) { | 
| 85 |  |  |  |  |  |  | return _interpolate( | 
| 86 | 8 |  |  |  |  | 43 | $endpoints{$name}->to_abs->to_string, | 
| 87 |  |  |  |  |  |  | \%values, | 
| 88 |  |  |  |  |  |  | $values | 
| 89 |  |  |  |  |  |  | ); | 
| 90 |  |  |  |  |  |  | }; | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | # The following is based on url_for of Mojolicious::Controller | 
| 93 |  |  |  |  |  |  | # and parts of path_for in Mojolicious::Routes::Route | 
| 94 |  |  |  |  |  |  | # Get match object | 
| 95 | 39 |  |  |  |  | 70 | my $match; | 
| 96 | 39 | 50 |  |  |  | 130 | unless ($match = $c->match) { | 
| 97 | 0 |  |  |  |  | 0 | $match = Mojolicious::Routes::Match->new(get => '/'); | 
| 98 | 0 |  |  |  |  | 0 | $match->root($c->app->routes); | 
| 99 |  |  |  |  |  |  | }; | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | # Base | 
| 102 | 39 |  |  |  |  | 853 | my $url = Mojo::URL->new; | 
| 103 | 39 |  |  |  |  | 412 | my $req = $c->req; | 
| 104 | 39 |  |  |  |  | 831 | $url->base($req->url->base->clone); | 
| 105 | 39 |  |  |  |  | 1777 | my $base = $url->base; | 
| 106 | 39 |  |  |  |  | 246 | $base->userinfo(undef); | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | # Get parameters | 
| 109 | 39 |  |  |  |  | 244 | my $param = $endpoints{$name}; | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | # Set parameters to url | 
| 112 | 39 | 100 |  |  |  | 143 | $url->scheme($param->{scheme}) if $param->{scheme}; | 
| 113 | 39 | 100 |  |  |  | 148 | $url->port($param->{port}) if $param->{port}; | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 39 | 100 |  |  |  | 117 | if ($param->{host}) { | 
| 116 | 18 |  |  |  |  | 67 | $url->host($param->{host}); | 
| 117 | 18 | 100 |  |  |  | 161 | $url->port(undef) unless $param->{port}; | 
| 118 | 18 | 100 |  |  |  | 119 | $url->scheme('http') unless $url->scheme; | 
| 119 |  |  |  |  |  |  | }; | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | # Clone query | 
| 122 | 39 | 100 |  |  |  | 308 | $url->query( [@{$param->{query}}] ) if $param->{query}; | 
|  | 28 |  |  |  |  | 206 |  | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | # Get path | 
| 125 | 39 |  |  |  |  | 1646 | my $path = $url->path; | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | # Lookup match | 
| 128 | 39 |  |  |  |  | 688 | my $r = $match->root->find($param->{route}); | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | # Interpolate path | 
| 131 | 39 |  |  |  |  | 4449 | my @parts; | 
| 132 | 39 |  |  |  |  | 139 | while ($r) { | 
| 133 | 84 |  |  |  |  | 306 | my $p = ''; | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 84 |  |  |  |  | 140 | foreach my $part (@{$r->pattern->tree}) { | 
|  | 84 |  |  |  |  | 195 |  | 
| 136 | 71 |  |  |  |  | 432 | my $t = $part->[0]; | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | # Slash | 
| 139 | 71 | 100 |  |  |  | 277 | if ($t eq 'slash') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 140 | 19 |  |  |  |  | 41 | $p .= '/'; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | # Text | 
| 144 |  |  |  |  |  |  | elsif ($t eq 'text') { | 
| 145 | 32 |  |  |  |  | 91 | $p .= $part->[1]; | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | # Various wildcards | 
| 149 |  |  |  |  |  |  | elsif ($t =~ m/^(?:wildcard|placeholder|relaxed)$/) { | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | # Support different tree variants in Mojolicious (before and after 9) | 
| 152 | 20 | 50 |  |  |  | 66 | my $n = ref $part->[1] eq 'ARRAY' ? $part->[1]->[0] : $part->[1]; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 20 | 50 |  |  |  | 58 | if (exists $values{$part->[1]}) { | 
| 155 | 0 |  |  |  |  | 0 | $p .= $values{$n}; | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  | else { | 
| 158 | 20 |  |  |  |  | 61 | $p .= "{$n}"; | 
| 159 |  |  |  |  |  |  | }; | 
| 160 |  |  |  |  |  |  | }; | 
| 161 |  |  |  |  |  |  | }; | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | # Prepend to path array | 
| 164 | 84 |  |  |  |  | 423 | unshift(@parts, $p); | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | # Go up one level till root | 
| 167 | 84 |  |  |  |  | 220 | $r = $r->parent; | 
| 168 |  |  |  |  |  |  | }; | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | # Set path | 
| 171 | 39 | 50 |  |  |  | 348 | $path->parse(join('', @parts)) if @parts; | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | # Fix trailing slash | 
| 174 | 39 | 50 | 33 |  |  | 557 | $path->trailing_slash(1) | 
|  |  |  | 33 |  |  |  |  | 
| 175 |  |  |  |  |  |  | if (!$name || $name eq 'current') | 
| 176 |  |  |  |  |  |  | && $req->url->path->trailing_slash; | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | # Make path absolute | 
| 179 | 39 |  |  |  |  | 114 | my $base_path = $base->path; | 
| 180 | 39 |  |  |  |  | 562 | unshift @{$path->parts}, @{$base_path->parts}; | 
|  | 39 |  |  |  |  | 114 |  | 
|  | 39 |  |  |  |  | 2723 |  | 
| 181 | 39 |  |  |  |  | 1597 | $base_path->parts([]); | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | # Interpolate url for query parameters | 
| 184 | 39 |  |  |  |  | 478 | return _interpolate($url->to_abs->to_string, \%values, $values); | 
| 185 |  |  |  |  |  |  | } | 
| 186 | 2 |  |  |  |  | 252 | ); | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | # Add 'get_endpoints' helper | 
| 190 |  |  |  |  |  |  | $mojo->helper( | 
| 191 |  |  |  |  |  |  | get_endpoints => sub { | 
| 192 | 1 |  |  | 1 |  | 146 | my $c = shift; | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | # Get all endpoints | 
| 195 | 1 |  |  |  |  | 4 | my %endpoint_hash; | 
| 196 | 1 |  |  |  |  | 7 | foreach (keys %endpoints) { | 
| 197 | 7 |  |  |  |  | 33 | $endpoint_hash{$_} = $c->endpoint($_); | 
| 198 |  |  |  |  |  |  | }; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | # Return endpoint hash | 
| 201 | 1 |  |  |  |  | 7 | return \%endpoint_hash; | 
| 202 | 2 |  |  |  |  | 267 | }); | 
| 203 |  |  |  |  |  |  | }; | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | # Interpolate templates | 
| 207 |  |  |  |  |  |  | sub _interpolate { | 
| 208 | 53 |  |  | 53 |  | 26404 | my $endpoint = shift; | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | # Decode escaped symbols | 
| 211 | 53 |  |  |  |  | 310 | $endpoint =~ | 
| 212 | 106 |  |  |  |  | 2109 | s/\%7[bB](.+?)\%7[dD]/'{' . b($1)->url_unescape . '}'/ge; | 
| 213 |  |  |  |  |  |  |  | 
| 214 | 53 |  |  |  |  | 1062 | my $param = shift; | 
| 215 | 53 |  |  |  |  | 108 | my $orig_param = shift; | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | # Interpolate template | 
| 218 | 53 |  |  |  |  | 176 | pos($endpoint) = 0; | 
| 219 | 53 |  |  |  |  | 306 | while ($endpoint =~ /\{([^\}\?}\?]+)\??\}/g) { | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | # Save search position | 
| 222 |  |  |  |  |  |  | # Todo: That's not exact! | 
| 223 | 117 |  |  |  |  | 514 | my $val = $1; | 
| 224 | 117 |  |  |  |  | 255 | my $p = pos($endpoint) - length($val) - 1; | 
| 225 |  |  |  |  |  |  |  | 
| 226 | 117 |  |  |  |  | 204 | my $fill = undef; | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | # Look in param | 
| 229 | 117 | 100 |  |  |  | 355 | if ($param->{$val}) { | 
|  |  | 100 |  |  |  |  |  | 
| 230 | 37 |  |  |  |  | 121 | $fill = b($param->{$val})->url_escape; | 
| 231 | 37 |  |  |  |  | 1651 | $endpoint =~ s/\{$val\??\}/$fill/; | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | # unset specific parameters | 
| 235 |  |  |  |  |  |  | elsif (exists $orig_param->{$val}) { | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | # Delete specific parameters | 
| 238 | 3 |  |  |  |  | 9 | for ($endpoint) { | 
| 239 | 3 | 100 |  |  |  | 148 | if (s/(?<=[\&\?])[^\}][^=]*?=\{$val\??\}//g) { | 
| 240 | 2 |  |  |  |  | 16 | s/([\?\&])\&*/$1/g; | 
| 241 | 2 |  |  |  |  | 5 | s/\&$//g; | 
| 242 |  |  |  |  |  |  | }; | 
| 243 | 3 |  |  |  |  | 73 | s/^([^\?]+?)([\/\.])\{$val\??\}\2/$1$2/g; | 
| 244 | 3 |  |  |  |  | 50 | s/^([^\?]+?)\{$val\??\}/$1/g; | 
| 245 |  |  |  |  |  |  | }; | 
| 246 |  |  |  |  |  |  | }; | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | # Reset search position | 
| 249 |  |  |  |  |  |  | # Todo: (not exact if it was optional) | 
| 250 | 117 |  | 100 |  |  | 815 | pos($endpoint) = $p + length($fill || ''); | 
| 251 |  |  |  |  |  |  | }; | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | # Ignore optional placeholders | 
| 254 | 53 | 100 | 66 |  |  | 348 | if (exists $param->{'?'} && | 
| 255 |  |  |  |  |  |  | !defined $param->{'?'}) { | 
| 256 | 7 |  |  |  |  | 21 | for ($endpoint) { | 
| 257 | 7 | 50 |  |  |  | 83 | s/(?<=[\&\?])[^\}][^=]*?=\{[^\?\}]+?\?\}//g or last; | 
| 258 | 7 |  |  |  |  | 50 | s/([\?\&])\&*/$1/g; | 
| 259 | 7 |  |  |  |  | 30 | s/\&$//g; | 
| 260 |  |  |  |  |  |  | }; | 
| 261 |  |  |  |  |  |  | }; | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | # Strip empty query marker | 
| 264 | 53 |  |  |  |  | 128 | $endpoint =~ s/\?$//; | 
| 265 | 53 |  |  |  |  | 807 | return $endpoint; | 
| 266 |  |  |  |  |  |  | }; | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | 1; | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | __END__ |