From a59630771f33c28f52cf947dc26a53e187d1252f Mon Sep 17 00:00:00 2001 From: Andinus Date: Tue, 28 Mar 2023 10:13:34 +0530 Subject: Add initial design, basic test --- lib/Template/Nest/Fast.rakumod | 111 +++++++++++++++++++++++++++++++++++++++++ t/00-basic.rakutest | 5 ++ 2 files changed, 116 insertions(+) create mode 100644 lib/Template/Nest/Fast.rakumod create mode 100644 t/00-basic.rakutest diff --git a/lib/Template/Nest/Fast.rakumod b/lib/Template/Nest/Fast.rakumod new file mode 100644 index 0000000..1b718ce --- /dev/null +++ b/lib/Template/Nest/Fast.rakumod @@ -0,0 +1,111 @@ +#| Template::Nest::Fast is a high-performance template engine module +#| for Raku, designed to process nested templates quickly and +#| efficiently. This module improves on the original Template::Nest +#| module by caching the index of positions of variables, resulting in +#| significantly faster processing times. +class Template::Nest::Fast { + # has Str @!token-delims = ['']; + has Str $!name-label = 'TEMPLATE'; + has IO $.template-dir is required; + + # Template objects after compilation. + has %!templates; + + #| TWEAK reads all the files in template-dir ending with '.html' + #| extension and compiles them. + submethod TWEAK() { + # Grab all files ending with .html recursively. + my IO @stack = $!template-dir, ; + my IO @templates = gather while @stack { + with @stack.pop { + when :d { @stack.append: .dir } + .take when .extension.lc eq 'html'; + } + } + + # Render all the files. + self.compile($_) for @templates; + } + + #| compile reads a template and prepares it for render. + method compile(IO $template) { + # Get template name relative to $!template-dir and remove + # `.html` extension. + my Str $t = $template.relative($!template-dir).substr(0, *-5); + + my Str $f = $template.slurp; + + # Store the template path. + %!templates{$t} = $template; + + # Capture the start, end delim and the variable inside it. DO NOT + # backtrack. + with $f ~~ m:g/(''):/ -> @m { + # Initialize with an empty list. + %!templates{$t} = []; + + # For every match we have start, end position of each + # delim and the variable. + # + # We sort @m by the start delim's position. (Does some + # magic, I have to comment it) + for @m.sort(*[0].from) -> $m { + # Store each variable alongside it's template file in + # %!templates. + push %!templates{$t}, %( + name => ($m[1].Str), + start-delim => ($m[0].from, $m[0].to), + variable => ($m[1].from, $m[1].to), + end-delim => ($m[2].from, $m[2].to), + # Length of the string to replace. + length => ($m[2].to - $m[0].from), + ); + } + } + } + + #| parse consumes values of keys of the template object and + #| returns the final string that needs to be replaced with that + #| key. + #| + #| my %t = %( TEMPLATE => 'test', xyz => 'hi' ) + #| + #| parse here consumes 'xyz' and returns 'hi', it can also handle + #| keys where the value is another Hash or a List. + method parse($var --> Str) { + given $var { + when Str { return $var } + when Hash { return self.render($var) } + when List { return $var.map({self.render($_)}).join } + } + } + + method render(%t --> Str) { + my Str $rendered; + + # After mutating the rendered string the positions of those + # other variables that need to be substituted changes and so + # we need to recalculate it, that is stored in this var. + my int $delta = 0; + + with (%!templates{%t{$!name-label}}) -> %t-compiled { + $rendered = %t-compiled.slurp; + + for @(%t-compiled) -> %v { + die "Variable {%v} not defined." unless %t{%v}; + + # Replace the template variable. + with self.parse(%t{%v}) -> $append { + $rendered.substr-rw(%v[0] + $delta, %v) = $append; + + # From delta remove %v and add the length + # of string we just appended. + $delta += - %v + $append.chars; + } + } + } else { + die "Unrecognized template: {%t{$!name-label}}."; + } + return $rendered; + } +} diff --git a/t/00-basic.rakutest b/t/00-basic.rakutest new file mode 100644 index 0000000..bcd096f --- /dev/null +++ b/t/00-basic.rakutest @@ -0,0 +1,5 @@ +use Test; + +plan 1; + +use-ok 'Template::Nest::Fast'; -- cgit 1.4.1-2-gfad0