Skip to content

Commit

Permalink
Make template live reload account for dependencies
Browse files Browse the repository at this point in the history
Fixes #57.
  • Loading branch information
jnthn committed Jul 15, 2022
1 parent cc2e2eb commit cee6b22
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 10 deletions.
4 changes: 3 additions & 1 deletion lib/Cro/WebApp/Template/AST.pm6
Expand Up @@ -13,6 +13,8 @@ my role ContainerNode does Node {
}

my class Template does ContainerNode is export {
has @.used-files;

method compile() {
my $*IN-SUB = False;
my $children-compiled = @!children.map(*.compile).join(", ");
Expand All @@ -25,7 +27,7 @@ my class Template does ContainerNode is export {
}
my %*TEMPLATE-EXPORTS = :sub{}, :macro{};
my $renderer = EVAL 'sub ($_) { join "", (' ~ $children-compiled ~ ') }';
return { :$renderer, exports => %*TEMPLATE-EXPORTS };
return Map.new((:$renderer, exports => %*TEMPLATE-EXPORTS, :@!used-files));
}
}

Expand Down
5 changes: 4 additions & 1 deletion lib/Cro/WebApp/Template/ASTBuilder.pm6
Expand Up @@ -46,7 +46,9 @@ class Cro::WebApp::Template::ASTBuilder {
exported-subs => $loaded-prelude.exports<sub>.keys,
exported-macros => $loaded-prelude.exports<macro>.keys;
}
make Template.new(children => [|@prelude, |flatten-literals($<sequence-element>.map(*.ast))]);
make Template.new:
children => [|@prelude, |flatten-literals($<sequence-element>.map(*.ast))],
used-files => @*USED-FILES;
}

method sequence-element:sym<sigil-tag>($/) {
Expand Down Expand Up @@ -189,6 +191,7 @@ class Cro::WebApp::Template::ASTBuilder {
with $<file> {
my $template-name = .ast;
my $used = await $*TEMPLATE-REPOSITORY.resolve($template-name, @*TEMPLATE-LOCATIONS);
@*USED-FILES.push($used);
make UseFile.new: :path($used.path),
exported-subs => $used.exports<sub>.keys,
exported-macros => $used.exports<macro>.keys;
Expand Down
1 change: 1 addition & 0 deletions lib/Cro/WebApp/Template/Parser.pm6
Expand Up @@ -21,6 +21,7 @@ grammar Cro::WebApp::Template::Parser {
token TOP {
:my $*IN-ATTRIBUTE = False;
:my $*IN-MACRO = False;
:my @*USED-FILES;
<sequence-element>*
[ $ || <.panic: 'confused'> ]
}
Expand Down
40 changes: 35 additions & 5 deletions lib/Cro/WebApp/Template/Repository.pm6
Expand Up @@ -20,6 +20,9 @@ class Cro::WebApp::Template::Compiled is implementation-detail {
#| The source file for the template, if available.
has IO::Path $.path;

#| Files that are used by this template.
has Cro::WebApp::Template::Compiled @.used-files;

# Implementation details.
has &.renderer;
has %.exports;
Expand Down Expand Up @@ -77,7 +80,7 @@ monitor Cro::WebApp::Template::Repository::FileSystem does Cro::WebApp::Template
}
for @!global-search-paths {
my $path = .add($template-name);
return self.resolve-absolute($path, :@locations) if $path.f;
return self.resolve-absolute($path.absolute.IO, :@locations) if $path.f;
}
die X::Cro::WebApp::Template::NotFound.new(:$template-name);
}
Expand Down Expand Up @@ -110,17 +113,44 @@ monitor Cro::WebApp::Template::Repository::FileSystem does Cro::WebApp::Template
#| disk changes. Ideal for development time.
monitor Cro::WebApp::Template::Repository::FileSystem::Reloading is Cro::WebApp::Template::Repository::FileSystem {
has %!abs-path-to-mtime;
has %!dependencies;

#| Loads a template from an absolute path. If the file at that path didn't
#| change since the last template compilation, then the cached compilation of
#| the template is returned. Otherwise, it is recompiled.
#| change since the last template compilation, nor any of the templates
#| that it depends on, then the cached compilation of the template is
#| returned. Otherwise, it is recompiled.
method resolve-absolute(IO() $abs-path, :@locations --> Promise) {
my $modified = $abs-path.IO.modified;
my $modified = $abs-path.modified;
if (%!abs-path-to-mtime{$abs-path} // 0) != $modified {
self.refresh($abs-path)
}
elsif %!dependencies{$abs-path} -> @deps {
for @deps -> $dep-path {
my $dep-modified = $dep-path.modified;
if (%!abs-path-to-mtime{$dep-path} // 0) != $dep-modified {
self.refresh($abs-path);
last;
}
}
}
%!abs-path-to-mtime{$abs-path} = $modified;
callsame
my Promise $compiled-promise = callsame;
$compiled-promise.then: {
if $compiled-promise.status == Kept {
self.update-dependencies($compiled-promise.result);
}
}
$compiled-promise
}

method update-dependencies($compiled) {
sub collect-deps(Cro::WebApp::Template::Compiled $compiled) {
for $compiled.used-files -> $used {
take .absolute.IO with $used.path;
collect-deps($used);
}
}
%!dependencies{$compiled.path.absolute} = eager gather collect-deps($compiled);
}
}

Expand Down
22 changes: 19 additions & 3 deletions t/template-reload.t
@@ -1,12 +1,28 @@
BEGIN %*ENV<CRO_DEV> = True;
use Test;
use Cro::WebApp::Template;
plan 2;
plan 4;

my $test-file = $*PROGRAM.parent.add('test-data').add('reload.crotmp');
END try unlink $test-file;
$test-file.spurt: 'Hello <.name>.';
is render-template($test-file, {name => "World"}), 'Hello World.';
is render-template($test-file, {name => "World"}), 'Hello World.',
'Correct rendering before change';

$test-file.spurt: 'Goodbye <.name>!';
is render-template($test-file, {name => "dlroW"}), 'Goodbye dlroW!';
is render-template($test-file, {name => "dlroW"}), 'Goodbye dlroW!',
'Change was detected';

my $used-file = $*PROGRAM.parent.add('test-data').add('reload-used.crotmp');
END try unlink $used-file;
$used-file.spurt: "<:sub foo>yes!</:>";
my $using-file = $*PROGRAM.parent.add('test-data').add('reload-using.crotmp');
END try unlink $using-file;
$using-file.spurt: "<:use 'reload-used.crotmp'><&foo>";
template-location $*PROGRAM.parent.add('test-data');
is render-template($using-file, {}), 'yes!',
'Correct rendering involving `use` before change';

$used-file.spurt: "<:sub foo>no!</:>";
is render-template($using-file, {}), 'no!',
'Change in used module was detected';

0 comments on commit cee6b22

Please sign in to comment.