File Coverage

blib/lib/EntityModel/Web/Site.pm
Criterion Covered Total %
statement 9 74 12.1
branch 0 34 0.0
condition 0 3 0.0
subroutine 3 8 37.5
pod 2 4 50.0
total 14 123 11.3


line stmt bran cond sub pod time code
1             package EntityModel::Web::Site;
2             {
3             $EntityModel::Web::Site::VERSION = '0.004';
4             }
5             use EntityModel::Class {
6 1         24 host => 'string',
7             template => 'string',
8             layout => { type => 'array', subclass => 'EntityModel::Web::Layout' },
9             page => { type => 'array', subclass => 'EntityModel::Web::Page' },
10             page_by_name => { type => 'hash', subclass => 'EntityModel::Web::Page', scope => 'private', watch => { page => 'name' } },
11             url_string => 'hash',
12             url_regex => 'array',
13 1     1   938 };
  1         2  
14              
15             =head1 NAME
16              
17             EntityModel::Web::Site
18              
19             =head1 VERSION
20              
21             version 0.004
22              
23             =head1 SYNOPSIS
24              
25             my $web = EntityModel::Web->new;
26             my $req = EntityModel::Web::Request->new;
27             my $site = $web->find_site('somehost.example.com');
28             my $page = $site->find_page('http://somehost.example.com/some/page');
29             my $response = $page->handle_request($req);
30              
31             =head1 DESCRIPTION
32              
33             The site maintains a path map for string and regex paths:
34              
35             string => {
36             index.html => {},
37             documentation/index.html => {},
38             },
39             regex => [
40             tutorial/([^/]+)/perl.html => {}
41             ]
42              
43             When parsing a new page entry, the L method is used to identify the entry to
44             use for the path map.
45              
46             The / delimiter is added automatically unless the pathseparator parameter is given, in which case
47             this value will be used instead (can be used for cases such as C.
48              
49             Any path that matches a string path exactly (via hash lookup) will return that page without further checks.
50             If this match fails, the string is compared against the regex entries. Normally top-level pages should be
51             anchored to the start of the string.
52              
53             Using a prefix substring match may help for performance, although this would need to limit to non-metachars only
54             and only applies to the start-anchored regex entries.
55              
56             =cut
57              
58 1     1   594 use URI;
  1         3  
  1         18  
59 1     1   15 use Data::Dumper;
  1         2  
  1         1309  
60              
61             =head1 METHODS
62              
63             =cut
64              
65             sub new {
66 0     0 1   my $class = shift;
67 0           my $self = $class->SUPER::new;
68              
69             $self->page->add_watch($self->sap(sub {
70 0     0     my ($self) = shift;
71 0           my %args = @_;
72 0 0         $self->add_page_to_map($args{add}) if $args{add};
73 0 0         $self->remove_page_from_map($args{drop}) if $args{drop};
74 0           }));
75              
76 0           my %args = %{$_[0]};
  0            
77 0 0         if(my $host = delete $args{host}) {
78 0           $self->host($host);
79             }
80 0 0         if(my $template = delete $args{template}) {
81 0           $self->template($template);
82             }
83 0 0         if(my $layout = delete $args{layout}) {
84 0           $self->layout->push(EntityModel::Web::Layout->new(%$_)) for @$layout;
85             }
86 0 0         if(my $page_list = delete $args{page}) {
87 0           for (@$page_list) {
88             # Add all these pages and populate the path map, including any subpages.
89 0           my $page = EntityModel::Web::Page->new(%$_, site => $self);
90 0           $self->page->push($page);
91             }
92             }
93 0           return $self;
94             }
95              
96             sub add_page_to_map {
97 0     0 0   my ($self, $page) = @_;
98 0           my $path = $page->path;
99 0 0         $path = qr{$path} if $page->pathtype ne 'string';
100              
101 0           my $current = $page;
102 0           my $depth = 0;
103 0           while(my $parent = $current->parent) {
104 0 0         if(ref $path) { # are we a regex?
105 0           my $pp = $parent->path;
106 0           my $sep = $current->separator;
107 0 0         my $v = $parent->pathtype eq 'string' ? qr{\Q$pp$sep\E} : qr{$pp\Q$sep\E};
108 0           $path = qr{$v$path};
109             } else {
110 0           $path = join($current->separator, $parent->path, $path);
111             }
112 0           $current = $parent;
113 0           ++$depth;
114             }
115              
116             # Add the leading / unless we've overridden it
117 0           my $sep = $current->separator;
118 0 0         if(ref $path) { # are we a regex?
119 0           $path = qr{\Q$sep\E$path};
120 0           push @{$self->url_regex->[$depth]}, qr/$path/, $page;
  0            
121             } else {
122             # Only prefix if we don't already have the prefix /
123 0 0         $path = $sep . $path unless index($path, $sep) == 0;
124 0           $self->url_string->set($path, $page);
125             }
126             }
127              
128             sub remove_page_from_map {
129 0     0 0   my ($self, $page) = @_;
130             }
131              
132             =head2 C
133              
134             In list context, returns the captured regex elements if we had any.
135              
136             =cut
137              
138             sub page_from_uri {
139 0     0 1   my $self = shift;
140 0           my $uri = shift;
141              
142             # Exact match wins
143 0           my $path = $uri->path;
144 0           my $page = $self->url_string->get($path);
145 0           logDebug("Had [%s] for [%s]", $path, $page);
146 0 0         return $page if $page;
147              
148             # Try without extension
149             {
150 0           (my $path_basename = $path) =~ s/\.(\w+)$//;
  0            
151 0           my $type = $1;
152 0           $page = $self->url_string->get($path_basename);
153 0           logDebug("URL string lookup resulted in [%s]", $page);
154 0 0         return $page if $page;
155             }
156              
157             # Go through regex options
158 0           my @regex = map { @$_ } grep defined, reverse $self->url_regex->list;
  0            
159 0           while(@regex) {
160 0           my ($k, $v) = splice @regex, 0, 2;
161 0           logDebug("Looking for %s, %s from %s", $k, $v, $path);
162              
163 0 0         if($path =~ $k) {
164 0 0         return $v unless wantarray;
165              
166             # Extract the matches - should probably do this and the m{} check in a single step
167 0 0 0       my @data = $#+ ? map { (defined($-[$_]) && defined($+[$_])) ? substr($path, $-[$_], $+[$_] - $-[$_]) : '' } 1..$#+ : ();
  0 0          
168 0           return ($v, @data);
169             }
170             }
171              
172 0           return undef;
173             }
174              
175             1;
176              
177             __END__