/
Builder.pm6
162 lines (133 loc) · 4.26 KB
/
Builder.pm6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use v6;
class X::Smack::Builder is Exception { }
class X::Smack::Builder::NoBuilder is X::Smack::Builder {
has Str $.sub;
method message() {
"$.sub must be called inside a builder \{} block"
}
}
class X::Smack::Builder::NoApp is X::Smack::Builder {
method message() {
'no application to build. Your builder {} block must either use mount() or return an app';
}
}
# Catching this will result in an app where all the mount() apps are ignored.
# Plack treated this as a warning. I treat it as an error for two reasons:
# (1) It really implies a mistake that will cause confusion, even with the
# warning. We might as well stop execution.
# (2) Exception handling in Perl 6 is amazingly better than Perl 5, so if they
# really feel like doing this for some reason, let them catch the
# exception and live with the consequences.
class X::Smack::Builder::UselessMount is X::Smack::Builder {
method message() {
"you used mount() in a builder \{} block, but the result of the block is an app, which hides all mounts; if this is deliberate, please catch the $?PACKAGE.perl() exception and .resume"
}
}
class Smack::Builder {
use Smack::App::URLMap;
has Callable @.builder-cbs;
has Smack::App::URLMap $!urlmap;
multi method add-middleware(&builder-cb) {
push @.builder-cbs, &builder-cb;
return;
}
multi method add-middleware($mw-class, |args) {
self.add-middleware: -> &app {
$mw-class.wrap-that(&app, |args);
}
}
multi method add-middleware-if(Mu $cond, &builder-cb) {
use Smack::Middleware::Conditional;
push @.builder-cbs, -> &app {
Smack::Middleware::Conditional.wrap-that(&app,
condition => $cond,
builder => &builder-cb,
);
}
return;
}
multi method add-middleware-if(Mu $cond, $mw-class, |args) {
self.add-middleware-if: $cond, -> &app {
$mw-class.wrap-that(&app, |args);
}
}
multi method mount($location, &app) {
$!urlmap .= new without $!urlmap;
$!urlmap.mount($location, &app);
return;
}
multi method mount($location, $app where *.^can('to-app')) {
self.mount($location, $app.to-app);
}
method is-mount-used() { defined $!urlmap }
# This should work fine if you want to allow mount() and an app in your
# build block. The consequence is that all mount()s are ignored.
# CATCH { when X::Smack::Builder::UselessMount { .resume } }
method to-app($app?) {
with $app {
die X::Smack::Builder::UselessMount.new
if $.is-mount-used;
self.wrap-that($app);
}
elsif $.is-mount-used {
self.wrap-that($!urlmap.to-app);
}
else {
die X::Smack::Builder::NoApp.new;
}
}
method wrap-that(&app is copy) {
for @.builder-cbs.reverse -> &builder-cb {
&app = builder-cb(&app);
}
&app;
}
}
proto enable(|) is export { * }
multi enable(&builder-cb) {
with $*SMACK-BUILDER {
.add-middleware(&builder-cb);
}
else {
die X::Smack::Builder::NoBuilder.new(sub => "enable");
}
}
multi enable($mw-class, |args) {
with $*SMACK-BUILDER {
.add-middleware($mw-class, |args);
}
else {
die X::Smack::Builder::NoBuilder.new(sub => "enable");
}
}
proto enable-if(|) is export { * }
multi enable-if(Mu $match, &builder-cb) {
with $*SMACK-BUILDER {
.add-middleware-if($match, &builder-cb)
}
else {
die X::Smack::Builder::NoBuilder.new(sub => "enable-if");
}
}
multi enable-if(Mu $match, $mw-class, |args) {
with $*SMACK-BUILDER {
.add-middleware-if($match, $mw-class, |args);
}
else {
die X::Smack::Builder::NoBuilder.new(sub => "enable-if");
}
}
sub mount(Pair $map) is export {
with $*SMACK-BUILDER {
.mount($map.key, $map.value);
}
else {
die X::Smack::Builder::NoBuilder.new(sub => "mount");
}
}
sub builder(&build-block) is export {
my $*SMACK-BUILDER = Smack::Builder.new;
my $app = build-block();
$app = $app.to-app if $app.defined && $app.^can('to-app');
$*SMACK-BUILDER.to-app($app);
}