X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FWeb%2FSimple%2FDispatchParser.pm;h=b580a91abf7557d68e5cc642f020359efaa61344;hb=6c0f599aa455ebf62c0581ef7ff8e9be5af5924e;hp=1882c9812208a7c287b14e1d3caa7149247983e5;hpb=da8429c93d5e013a72456da945681cb2a84aaf80;p=catagits%2FWeb-Simple.git diff --git a/lib/Web/Simple/DispatchParser.pm b/lib/Web/Simple/DispatchParser.pm index 1882c98..b580a91 100644 --- a/lib/Web/Simple/DispatchParser.pm +++ b/lib/Web/Simple/DispatchParser.pm @@ -3,6 +3,17 @@ package Web::Simple::DispatchParser; use strict; use warnings FATAL => 'all'; +sub DEBUG () { 0 } + +BEGIN { + if ($ENV{WEB_SIMPLE_DISPATCHPARSER_DEBUG}) { + no warnings 'redefine'; + *DEBUG = sub () { 1 } + } +} + +sub diag { if (DEBUG) { warn $_[0] } } + sub new { bless({}, ref($_[0])||$_[0]) } sub _blam { @@ -58,6 +69,35 @@ sub _parse_spec { } } +sub _parse_spec_combinator { + my ($self, $spec, $match) = @_; + for ($_[1]) { + + /\G\+/gc and + return $match; + + /\G\|/gc and + return do { + my @match = $match; + PARSE: { do { + push @match, $self->_parse_spec_section($_) + or $self->_blam("Unable to work out what the next section is"); + last PARSE if (pos == length); + last PARSE unless /\G\|/gc; # give up when next thing isn't | + } until (pos == length) }; # accept trailing whitespace + return sub { + foreach my $try (@match) { + if (my @ret = $try->(@_)) { + return @ret; + } + } + return; + }; + }; + } + return; +} + sub _parse_spec_section { my ($self) = @_; for ($_[1]) { @@ -92,37 +132,16 @@ sub _parse_spec_section { return; }; }; - } - return; # () will trigger the blam in our caller -} -sub _parse_spec_combinator { - my ($self, $spec, $match) = @_; - for ($_[1]) { - - /\G\+/gc and - return $match; + # ? + /\G\?/gc and + return $self->_parse_param_handler($_, 'query'); - /\G\|/gc and - return do { - my @match = $match; - PARSE: { do { - push @match, $self->_parse_spec_section($_) - or $self->_blam("Unable to work out what the next section is"); - last PARSE if (pos == length); - last PARSE unless /\G\|/gc; # give up when next thing isn't | - } until (pos == length) }; # accept trailing whitespace - return sub { - foreach my $try (@match) { - if (my @ret = $try->(@_)) { - return @ret; - } - } - return; - }; - }; + # % + /\G\%/gc and + return $self->_parse_param_handler($_, 'body'); } - return; + return; # () will trigger the blam in our caller } sub _http_method_match { @@ -168,7 +187,7 @@ sub _url_path_segment_match { my ($self) = @_; for ($_[1]) { # trailing / -> require / on end of URL - /\G(?:(?=\s)|$)/gc and + /\G(?:(?=[+|\)])|$)/gc and return '$'; # word chars only -> exact path part match /\G(\w+)/gc and @@ -204,4 +223,102 @@ sub _url_extension_match { } } +sub _parse_param_handler { + my ($self, $spec, $type) = @_; + + require Web::Simple::ParamParser; + my $unpacker = Web::Simple::ParamParser->can("get_unpacked_${type}_from"); + + for ($_[1]) { + my (@required, @single, %multi, $star, $multistar, %positional, $have_kw); + my $pos_idx = 0; + PARAM: { do { + + # ?:foo or ?@:foo + + my $is_kw = /\G\:/gc; + + # ?@foo or ?@* + + my $multi = /\G\@/gc; + + # @* or * + + if (/\G\*/gc) { + + $self->_blam("* is always named; no need to supply :") if $is_kw; + + $multi ? ($multistar = 1) : ($star = 1); + + $have_kw = 1; + + if ($star && $multistar) { + $self->_blam("Can't use * and \@* in the same parameter match"); + } + } else { + + # @foo= or foo= or @foo~ or foo~ + + /\G(\w+)/gc or $self->_blam('Expected parameter name'); + + my $name = $1; + + # check for = or ~ on the end + + /\G\=/gc + ? push(@required, $name) + : (/\G\~/gc or $self->_blam('Expected = or ~ after parameter name')); + + # record the key in the right category depending on the multi (@) flag + + $multi ? ($multi{$name} = 1) : (push @single, $name); + + # record positional or keyword + + $is_kw ? ($have_kw = 1) : ($positional{$name} = $pos_idx++); + } + } while (/\G\&/gc) } + + return sub { + my $raw = $unpacker->($_[0]); + foreach my $name (@required) { + return unless exists $raw->{$name}; + } + my (%p, %done); + my @p = (undef) x $pos_idx; + foreach my $name ( + @single, + ($star + ? (grep { !exists $multi{$_} } keys %$raw) + : () + ) + ) { + if (exists $raw->{$name}) { + if (exists $positional{$name}) { + $p[$positional{$name}] = $raw->{$name}->[-1]; + } else { + $p{$name} = $raw->{$name}->[-1]; + } + } + $done{$name} = 1; + } + foreach my $name ( + keys %multi, + ($multistar + ? (grep { !exists $done{$_} && !exists $multi{$_} } keys %$raw) + : () + ) + ) { + if (exists $positional{$name}) { + $p[$positional{$name}] = $raw->{$name}||[]; + } else { + $p{$name} = $raw->{$name}||[]; + } + } + $p[$pos_idx] = \%p if $have_kw; + return ({}, @p); + }; + } +} + 1;