no reason for member_type to build lazily, it should be required
[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     confess "No top skin object provided" unless $args->{top_skin};
27     $self->_load_file($self->source_file, $args);
28     unless ($self->has_widget_class) {
29       $self->widget_class($args->{skin}->widget_class_for($self));
30     }
31   };
32
33   implements 'widget_order_for' => as {
34     my ($self, $name) = @_;
35     return (
36       ($self->has_layout($name)
37         ? ([ $self->widget_class, $self ]) #;
38         : ()),
39       ($self->has_super
40         ? ($self->super->widget_order_for($name))
41         : ()),
42     );
43   };
44
45   implements 'layout_names' => as {
46     my ($self) = @_;
47     my %seen;
48     return [
49       grep { !$seen{$_}++ }
50         keys %{shift->layouts},
51         ($self->has_super
52           ? (@{$self->super->layout_names})
53           : ())
54     ];
55   };
56
57   implements 'has_layout' => as { exists $_[0]->layouts->{$_[1]} };
58
59   implements '_load_file' => as {
60     my ($self, $file, $build_args) = @_;
61     my $data = $file->slurp;
62     my $layouts = $self->layouts;
63     # cheesy match for "=for layout name ... =something"
64     # final split group also handles last in file, (?==) is lookahead
65     # assertion for '=' so "=for layout name1 ... =for layout name2"
66     # doesn't have the match pos go past the latter = and lose name2
67     while ($data =~ m/=(.*?)\n(.*?)(?:\n(?==)|$)/sg) {
68       my ($data, $text) = ($1, $2);
69       if ($data =~ /^for layout (\S+)/) {
70         my $fname = $1;
71         $text =~ s/^(?:\s*\r?\n)+//; #remove leading empty lines
72         $text =~ s/[\s\r\n]+$//;     #remove trailing whitespace
73         $layouts->{$fname} = $text;
74       } elsif ($data =~ /^extends (\S+)/) {
75         my $super_name = $1;
76         my $skin;
77         if ($super_name eq 'NEXT') {
78           confess "No next skin and layout extends NEXT"
79             unless $build_args->{next_skin};
80           $skin = $build_args->{next_skin};
81           $super_name = $self->name;
82         } else {
83           $skin = $build_args->{top_skin};
84         }
85         $self->super($skin->create_layout_set($super_name));
86       } elsif ($data =~ /^widget (\S+)/) {
87         my $widget_type = $1;
88         $self->widget_type($1);
89       } elsif ($data =~ /^cut/) {
90         # no-op
91       } else {
92         confess "Unparseable directive ${data} in ${file}";
93       }
94     }
95   };
96
97   implements '_build_widget_type' => as {
98     my ($self) = @_;
99     my $widget = join('',   map { ucfirst($_) } split('_', $self->name));
100     $widget    = join('::', map { ucfirst($_) } split('/', $widget));
101
102     #print STDERR "--- ", $self->name, " maps to widget $widget \n";
103
104     return $widget;
105   };
106
107 };
108
109 1;