77a0dac663c38ad922b0a733d9ac4dd6a9cac0bf
[catagits/Reaction.git] / lib / Reaction / UI / LayoutSet.pm
1 package Reaction::UI::LayoutSet;
2
3 use Reaction::Class;
4 use File::Spec;
5
6 class LayoutSet which {
7
8   has 'layouts' => (is => 'ro', default => sub { {} });
9
10   has 'name' => (is => 'ro', required => 1);
11
12   has 'source_file' => (is => 'ro', required => 1);
13
14   has 'widget_class' => (
15     is => 'rw', lazy_fail => 1, predicate => 'has_widget_class'
16   );
17
18   has 'widget_type' => (is => 'rw', lazy_build => 1);
19
20   has 'super' => (is => 'rw', predicate => 'has_super');
21
22   implements 'BUILD' => as {
23     my ($self, $args) = @_;
24     my @path = @{$args->{search_path}||[]};
25     confess "No skin object provided" unless $args->{skin};
26     $self->_load_file($self->source_file, $args);
27     unless ($self->has_widget_class) {
28       $self->widget_class($args->{skin}->widget_class_for($self));
29     }
30   };
31
32   implements 'widget_order_for' => as {
33     my ($self, $name) = @_;
34     return (
35       ($self->has_layout($name)
36         ? ([ $self->widget_class, $self ]) #;
37         : ()),
38       ($self->has_super
39         ? ($self->super->widget_order_for($name))
40         : ()),
41     );
42   };
43
44   implements 'layout_names' => as {
45     my ($self) = @_;
46     my %seen;
47     return [
48       grep { !$seen{$_}++ }
49         keys %{shift->layouts},
50         ($self->has_super
51           ? (@{$self->super->layout_names})
52           : ())
53     ];
54   };
55
56   implements 'has_layout' => as { exists $_[0]->layouts->{$_[1]} };
57
58   implements '_load_file' => as {
59     my ($self, $file, $build_args) = @_;
60     my $data = $file->slurp;
61     my $layouts = $self->layouts;
62     # cheesy match for "=for layout name ... =something"
63     # final split group also handles last in file, (?==) is lookahead
64     # assertion for '=' so "=for layout name1 ... =for layout name2"
65     # doesn't have the match pos go past the latter = and lose name2
66     while ($data =~ m/=(.*?)\n(.*?)(?:\n(?==)|$)/sg) {
67       my ($data, $text) = ($1, $2);
68       if ($data =~ /^for layout (\S+)/) {
69         my $fname = $1;
70         $text =~ s/^(?:\s*\r?\n)+//; #remove leading empty lines
71         $text =~ s/[\s\r\n]+$//;     #remove trailing whitespace
72         $layouts->{$fname} = $text;
73       } elsif ($data =~ /^extends (\S+)/) {
74         my $super_name = $1;
75         my $skin;
76         if ($super_name eq 'NEXT') {
77           confess "No next skin and layout extends NEXT"
78             unless $build_args->{next_skin};
79           $skin = $build_args->{next_skin};
80           $super_name = $self->name;
81         } else {
82           $skin = $build_args->{skin};
83         }
84         $self->super($skin->create_layout_set($super_name));
85       } elsif ($data =~ /^widget (\S+)/) {
86         my $widget_type = $1;
87         $self->widget_type($1);
88       } elsif ($data =~ /^cut/) {
89         # no-op
90       } else {
91         confess "Unparseable directive ${data}";
92       }
93     }
94   };
95
96   implements '_build_widget_type' => as {
97     my ($self) = @_;
98     my $widget = join('',   map { ucfirst($_) } split('_', $self->name));
99     $widget    = join('::', map { ucfirst($_) } split('/', $widget));
100
101     #print STDERR "--- ", $self->name, " maps to widget $widget \n";
102
103     return $widget;
104   };
105
106 };
107
108 1;