Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fragments #91

Merged
merged 16 commits into from
Nov 19, 2024
3 changes: 2 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Cro::WebApp ![Build Status](https://github.com/croservices/cro-webapp/actions/workflows/ci.yml/badge.svg)

This is a library to make it easier to build server-side web applications using
Cro. See the [Cro website](http://cro.services/) for further information and
documentation.
Cro. See the [Cro website](http://cro.raku.org/) for further information and
documentation.

librasteve marked this conversation as resolved.
Show resolved Hide resolved
... adding :fragments
145 changes: 145 additions & 0 deletions cro-webapp.iml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,152 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/t" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/t" isTestSource="true" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library name="CBOR::Simple" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:CBOR::Simple!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Base64" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Base64!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="JSON::JWT" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:JSON::JWT!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Digest::HMAC" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Digest::HMAC!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="HTTP::HPACK:ver&lt;1.0.0&gt;" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:HTTP::HPACK:ver&lt;1.0.0&gt;!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Log::Timeline" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Log::Timeline!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="TinyFloats:ver&lt;0.0.3+&gt;" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:TinyFloats:ver&lt;0.0.3+&gt;!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="OO::Monitors" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:OO::Monitors!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Cro::Core:ver&lt;0.8.9&gt;" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Cro::Core:ver&lt;0.8.9&gt;!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Cro::HTTP:ver&lt;0.8.9&gt;" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Cro::HTTP:ver&lt;0.8.9&gt;!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Crypt::Random" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Crypt::Random!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="DateTime::Parse" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:DateTime::Parse!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="Cro::TLS:ver&lt;0.8.9&gt;" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:Cro::TLS:ver&lt;0.8.9&gt;!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="JSON::Fast" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:JSON::Fast!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="IO::Socket::Async::SSL" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:IO::Socket::Async::SSL!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="IO::Path::ChildSecure" type="perl6">
<CLASSES />
<JAVADOC />
<SOURCES>
<root url="raku://1166070983:IO::Path::ChildSecure!/" />
</SOURCES>
</library>
</orderEntry>
librasteve marked this conversation as resolved.
Show resolved Hide resolved
</component>
</module>
20 changes: 10 additions & 10 deletions lib/Cro/WebApp/Template.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ my $template-part-plugin = router-plugin-register("template-part");

#| Render the template at the specified path using the specified data, and
#| return the result as a C<Str>.
multi render-template(IO::Path $template-path, $initial-topic, :%parts --> Str) is export {
multi render-template(IO::Path $template-path, $initial-topic, :%parts, :$fragment --> Str) is export {
my $compiled-template = await get-template-repository.resolve-absolute($template-path.absolute);
Cro::WebApp::LogTimeline::RenderTemplate.log: :template($template-path), {
render-internal($compiled-template, $initial-topic, %parts)
render-internal($compiled-template, $initial-topic, %parts, :$fragment)
}
}

#| Render the template at the specified path, which will be resolved either in the
#| resources or via the file system, as configured by C<template-location> or
#| C<templates-from-resources>.
multi render-template(Str $template, $initial-topic, :%parts --> Str) is export {
multi render-template(Str $template, $initial-topic, :%parts, :$fragment --> Str) is export {
# Gather the route-specific locations and turn them into location descriptors
# for the resolver to use.
my @route-locations := try { router-plugin-get-configs($template-location-plugin) } // ();
Expand All @@ -52,15 +52,15 @@ multi render-template(Str $template, $initial-topic, :%parts --> Str) is export

# Finally, render it.
Cro::WebApp::LogTimeline::RenderTemplate.log: :$template, {
render-internal($compiled-template, $initial-topic, %parts)
render-internal($compiled-template, $initial-topic, %parts, :$fragment)
}
}

sub render-internal($compiled-template, $initial-topic, %parts) {
sub render-internal($compiled-template, $initial-topic, %parts, :$fragment) {
my $*CRO-TEMPLATE-MAIN-PART := $initial-topic;
my %*CRO-TEMPLATE-EXPLICIT-PARTS := %parts;
my %*WARNINGS;
my $result = $compiled-template.render($initial-topic);
my $result = $compiled-template.render($initial-topic, :$fragment);
if %*WARNINGS {
for %*WARNINGS.kv -> $text, $number {
warn "$text ($number time{ $number == 1 ?? '' !! 's' })";
Expand Down Expand Up @@ -184,14 +184,14 @@ sub template-part(Str $name, &provider --> Nil) is export {
#| Used in a Cro::HTTP::Router route handler to render a template and set it as
#| the response body. The initial topic is passed to the template to render. The
#| content type will default to text/html, but can be set explicitly also.
multi template($template, $initial-topic, :%parts, :$content-type = 'text/html' --> Nil) is export {
multi template($template, $initial-topic, :%parts, :$content-type = 'text/html', :$fragment --> Nil) is export {
my @*CRO-TEMPLATE-PART-PROVIDERS := router-plugin-get-configs($template-part-plugin, error-sub => 'template');
content $content-type, render-template($template, $initial-topic, :%parts);
content $content-type, render-template($template, $initial-topic, :%parts, :$fragment);
}

#| Used in a Cro::HTTP::Router route handler to render a template and set it as
#| the response body. The content type will default to text/html, but can be set
#| explicitly also.
multi template($template, :%parts, :$content-type = 'text/html' --> Nil) is export {
template($template, Nil, :%parts, :$content-type);
multi template($template, :%parts, :$content-type = 'text/html', :$fragment --> Nil) is export {
template($template, Nil, :%parts, :$content-type, :$fragment);
}
52 changes: 48 additions & 4 deletions lib/Cro/WebApp/Template/AST.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ my role ContainerNode does Node {
my class Template does ContainerNode is export {
has @.used-files;

method compile() {
method compile(:$no-fragment) {
my $*IN-SUB = False;
my $*IN-FRAGMENT = False;
my $*NO-FRAGMENT = $no-fragment;
librasteve marked this conversation as resolved.
Show resolved Hide resolved
my $children-compiled = @!children.map(*.compile).join(", ");
use MONKEY-SEE-NO-EVAL;
multi sub trait_mod:<is>(Routine $r, :$TEMPLATE-EXPORT-SUB!) {
Expand All @@ -25,7 +27,10 @@ my class Template does ContainerNode is export {
multi sub trait_mod:<is>(Routine $r, :$TEMPLATE-EXPORT-MACRO!) {
%*TEMPLATE-EXPORTS<macro>{$r.name.substr('__TEMPLATE_MACRO__'.chars)} = $r;
}
my %*TEMPLATE-EXPORTS = :sub{}, :macro{};
multi sub trait_mod:<is>(Routine $r, :$TEMPLATE-EXPORT-FRAGMENT!) {
%*TEMPLATE-EXPORTS<fragment>{$r.name.substr('__TEMPLATE_FRAGMENT__'.chars)} = $r;
}
my %*TEMPLATE-EXPORTS = :sub{}, :macro{}, :fragment{};
my $renderer = EVAL 'sub ($_) { join "", (' ~ $children-compiled ~ ') }';
return Map.new((:$renderer, exports => %*TEMPLATE-EXPORTS, :@!used-files));
}
Expand Down Expand Up @@ -245,6 +250,41 @@ my class MacroApplication does ContainerNode is export {
}
}

my class TemplateFragment does ContainerNode is export {
has Str $.name is required;
has TemplateParameter @.parameters;

method compile() {

# if $*NO-FRAGMENT {
librasteve marked this conversation as resolved.
Show resolved Hide resolved
# return '(' ~ @!children.map(*.compile).join(", ") ~ ').join'
# }

my $should-export = !$*IN-FRAGMENT;
{
my $*IN-FRAGMENT = True;
my $params = @!parameters.map(*.compile).join(", ");
my $trait = $should-export ?? 'is TEMPLATE-EXPORT-FRAGMENT' !! '';

'(sub __TEMPLATE_FRAGMENT__' ~ $!name ~ "($params) $trait \{\n" ~
'join "", (' ~ @!children.map(*.compile).join(", ") ~ ')' ~
"} && '')\n"
~
', (' ~ @!children.map(*.compile).join(", ") ~ ').join'
}
}
}

my class FragmentCall does ContainerNode is export {
has Str $.target is required;
has Node @.arguments;

method compile() {
'__TEMPLATE_FRAGMENT__' ~ $!target ~ '(' ~ @!arguments.map(*.compile).join(", ") ~ ')'

}
}

my class TemplatePart does ContainerNode is export {
has Str $.name is required;
has TemplateParameter @.parameters;
Expand All @@ -267,11 +307,13 @@ my class UseFile does Node is export {
has IO::Path $.path is required;
has @.exported-subs;
has @.exported-macros;
has @.exported-fragments;

method compile() {
my $decls = join ",", flat
@!exported-subs.map(-> $sym { '(my &__TEMPLATE_SUB__' ~ $sym ~ ' = .<sub><' ~ $sym ~ '>)' }),
@!exported-macros.map(-> $sym { '(my &__TEMPLATE_MACRO__' ~ $sym ~ ' = .<macro><' ~ $sym ~ '>)' });
@!exported-macros.map(-> $sym { '(my &__TEMPLATE_MACRO__' ~ $sym ~ ' = .<macro><' ~ $sym ~ '>)' }),
@!exported-fragments.map(-> $sym { '(my &__TEMPLATE_FRAGMENT__' ~ $sym ~ ' = .<fragment><' ~ $sym ~ '>)' });
'(BEGIN (((' ~ $decls ~ ') given await($*TEMPLATE-REPOSITORY.resolve-absolute(\'' ~
$!path.absolute ~ '\'.IO)).exports) && ""))'
}
Expand All @@ -280,11 +322,13 @@ my class UseFile does Node is export {
my class Prelude does Node is export {
has @.exported-subs;
has @.exported-macros;
has @.exported-fragments;

method compile() {
my $decls = join ",", flat
@!exported-subs.map(-> $sym { '(my &__TEMPLATE_SUB__' ~ $sym ~ ' = .<sub><' ~ $sym ~ '>)' }),
@!exported-macros.map(-> $sym { '(my &__TEMPLATE_MACRO__' ~ $sym ~ ' = .<macro><' ~ $sym ~ '>)' });
@!exported-macros.map(-> $sym { '(my &__TEMPLATE_MACRO__' ~ $sym ~ ' = .<macro><' ~ $sym ~ '>)' }),
@!exported-fragments.map(-> $sym { '(my &__TEMPLATE_FRAGMENT__' ~ $sym ~ ' = .<fragment><' ~ $sym ~ '>)' });
'(BEGIN (((' ~ $decls ~ ') given await($*TEMPLATE-REPOSITORY.resolve-prelude()).exports) && ""))'
}
}
Expand Down
21 changes: 19 additions & 2 deletions lib/Cro/WebApp/Template/ASTBuilder.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class Cro::WebApp::Template::ASTBuilder {
my $loaded-prelude = await $*TEMPLATE-REPOSITORY.resolve-prelude();
@prelude[0] = Prelude.new:
exported-subs => $loaded-prelude.exports<sub>.keys,
exported-macros => $loaded-prelude.exports<macro>.keys;
exported-macros => $loaded-prelude.exports<macro>.keys,
exported-fragments => $loaded-prelude.exports<fragment>.keys;
}
make Template.new:
children => [|@prelude, |flatten-literals($<sequence-element>.map(*.ast))],
Expand Down Expand Up @@ -205,6 +206,21 @@ class Cro::WebApp::Template::ASTBuilder {
make MacroBody.new;
}

method sigil-tag:sym<fragment>($/) {
make TemplateFragment.new:
name => ~$<name>,
parameters => $<signature> ?? $<signature>.ast !! (),
children => flatten-literals($<sequence-element>.map(*.ast),
:trim-trailing-horizontal($*lone-end-line)),
trim-trailing-horizontal-before => $*lone-start-line;
}

method sigil-tag:sym<fragment-call>($/) {
make FragmentCall.new:
target => ~$<target>,
arguments => $<arglist> ?? $<arglist>.ast !! ();
}

method sigil-tag:sym<part>($/) {
make TemplatePart.new:
name => ~$<name>,
Expand All @@ -221,7 +237,8 @@ class Cro::WebApp::Template::ASTBuilder {
@*USED-FILES.push($used);
make UseFile.new: :path($used.path),
exported-subs => $used.exports<sub>.keys,
exported-macros => $used.exports<macro>.keys;
exported-macros => $used.exports<macro>.keys,
exported-fragments => $used.exports<fragment>.keys;
}
orwith $<library> {
my $module-name = .ast;
Expand Down
7 changes: 7 additions & 0 deletions lib/Cro/WebApp/Template/Library.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ sub template-library(*@resources) is export {
}
%exports{$mangled} := $sub;
}
for %template-exports<fragment>.kv -> $sym, $sub {
my $mangled = "&__TEMPLATE_FRAGMENT__$sym";
if %exports{$mangled}:exists {
die "Duplicate export of fragment '$sym' in $*TEMPLATE-FILE";
}
%exports{$mangled} := $sub;
}
}
return %exports;
}
Loading
Loading