perl bloggery.cgi / runs get request
[catagits/Web-Simple.git] / lib / Web / Simple / DispatchParser.pm
CommitLineData
920d6222 1package Web::Simple::DispatchParser;
2
3use strict;
4use warnings FATAL => 'all';
5
6sub new { bless({}, ref($_[0])||$_[0]) }
7
8sub _blam {
9 my ($self, $error) = @_;
10 my $hat = (' ' x pos).'^';
11 die "Error parsing dispatch specification: ${error}\n
12${_}
13${hat} here\n";
14}
15
16sub parse_dispatch_specification {
17 my ($self, $spec) = @_;
18 for ($spec) {
19 my @match;
20 local $self->{already_have};
21 /^\G\s*/; # eat leading whitespace
22 PARSE: { do {
23 push @match, $self->_parse_spec_section($spec)
24 or $self->_blam("Unable to work out what the next section is");
25 last PARSE if (pos == length);
9e4713ab 26 /\G\+/gc or $self->_blam('Spec sections must be separated by +');
920d6222 27 } until (pos == length) }; # accept trailing whitespace
28 return $match[0] if (@match == 1);
29 return sub {
30 my $env = { %{$_[0]} };
31 my $new_env;
32 my @got;
33 foreach my $match (@match) {
34 if (my @this_got = $match->($env)) {
35 my %change_env = %{shift(@this_got)};
36 @{$env}{keys %change_env} = values %change_env;
37 @{$new_env}{keys %change_env} = values %change_env;
38 push @got, @this_got;
39 } else {
40 return;
41 }
42 }
43 return ($new_env, @got);
44 };
45 }
46}
47
48sub _dupe_check {
49 my ($self, $type) = @_;
50 $self->_blam("Can't have more than one ${type} match in a specification")
51 if $self->{already_have}{$type};
52 $self->{already_have}{$type} = 1;
53}
54
55sub _parse_spec_section {
56 my ($self) = @_;
57 for ($_[1]) {
58
59 # GET POST PUT HEAD ...
60
61 /\G([A-Z]+)/gc and
62 return $self->_http_method_match($_, $1);
63
64 # /...
65
66 /\G(?=\/)/gc and
67 return $self->_url_path_match($_);
68
69 /\G\.(\w+)/gc and
70 return $self->_url_extension_match($_, $1);
71 }
72 return; # () will trigger the blam in our caller
73}
74
75sub _http_method_match {
76 my ($self, $str, $method) = @_;
77 $self->_dupe_check('method');
78 sub { shift->{REQUEST_METHOD} eq $method ? {} : () };
79}
80
81sub _url_path_match {
82 my ($self) = @_;
83 $self->_dupe_check('path');
84 for ($_[1]) {
85 my @path;
86 while (/\G\//gc) {
87 push @path, $self->_url_path_segment_match($_)
88 or $self->_blam("Couldn't parse path match segment");
89 }
90 my $re = '^()'.join('/','',@path).'$';
91 return sub {
92 if (my @cap = (shift->{PATH_INFO} =~ /$re/)) {
93 $cap[0] = {}; return @cap;
94 }
95 return ();
96 };
97 }
98 return;
99}
100
101sub _url_path_segment_match {
102 my ($self) = @_;
103 for ($_[1]) {
104 # trailing / -> require / on end of URL
105 /\G(?:(?=\s)|$)/gc and
106 return '$';
107 # word chars only -> exact path part match
108 /\G(\w+)/gc and
109 return "\Q$1";
110 # * -> capture path part
111 /\G\*/gc and
112 return '([^/]+)';
113 }
114 return ();
115}
116
117sub _url_extension_match {
118 my ($self, $str, $extension) = @_;
119 $self->_dupe_check('extension');
120 sub {
121 if ((my $tmp = shift->{PATH_INFO}) =~ s/\.\Q${extension}\E$//) {
122 ({ PATH_INFO => $tmp });
123 } else {
124 ();
125 }
126 };
127}
128
1291;