From 356016b39f901d63f02bf3ff31a1c3c638a822e8 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Tue, 19 Jul 2016 13:42:34 -0400 Subject: add guff/template --- src/guff/template.cr | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 src/guff/template.cr (limited to 'src') diff --git a/src/guff/template.cr b/src/guff/template.cr new file mode 100644 index 0000000..3068669 --- /dev/null +++ b/src/guff/template.cr @@ -0,0 +1,187 @@ +require "base64" + +# +# Crystal port of LuigiTemplate +# +# TODO: eventually this should be broken into a separate library +# +class Guff::Template + getter :string + + FILTERS = { + "h" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + HTML.escape(s) + }, + + "u" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + URI.escape(s) + }, + + "trim" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + s.strip + }, + + "uc" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + s.upcase + }, + + "lc" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + s.downcase + }, + + "reverse" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + s.reverse + }, + + "base64" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + Base64.encode(s) + }, + + "hash" => ->( + s : String, + args : Array(String), + row : Hash(String, String)) { + algo = (args.size > 0) ? args.first : "SHA1" + d = OpenSSL::Digest.new(algo) + d << s + d.hexdigest + }, + } of String => Proc(String, Array(String), Hash(String, String), String) + + class Cache + def initialize(@templates : Hash(String, String)) + @cache = {} of String => Template + end + + def [](key : String) : Template + raise "unknown template: #{key}" unless @templates[key]? + @cache[key] ||= Template.new(@templates[key]) + end + end + + class Filter + def initialize( + @name : String, + @args : Array(String) = [] of String + ) + end + + def run( + val : String, + row : Hash(String, String) + ) : String + raise "unknown filter: \"#{@name}\"" unless FILTERS.includes?(@name) + FILTERS[@name].call(val, @args, row) + end + end + + module Ops + abstract class Op + abstract def run(row : Hash(String, String)) : String + end + + class LiteralOp < Op + def initialize(@string : String) + end + + def run(row : Hash(String, String)) : String + @string + end + end + + class FilterOp < Op + def initialize(@key : String, @filters : Array(Filter)) + end + + def run(row : Hash(String, String)) : String + unless row.includes?(@key) + raise "missing template key: \"#{@key}\"" unless row.key?(@key) + end + + @filters.reduce(row[@key]) do |r, f| + f.run(r, row) + end + end + end + end + + def initialize(@string : String) + @ops = parse(@string) as Array(Ops::Op) + end + + def run(args : Hash(String, String)) + String.build do |io| + @ops.each do |op| + io << op.run(args) + end + end + end + + SCAN_RE = %r{ + # match opening brace + %\{ + + # optional whitespace + \s* + + # key + (?[^\s\|]+) + + # optional whitespace + \s* + + # filters + (?(\|(\s*[^\s\|\}]+\s*)+)*) + + # closing brace + \} + + | (?[^%]* | %) + }mx + + private def parse(string : String) : Array(Ops::Op) + r = string.scan(SCAN_RE).map do |md| + p md + if md["text"]? + Ops::LiteralOp.new(md["text"]) + else + filters = md["filters"].split(/\s*\|\s*/m) + filters.shift + + Ops::FilterOp.new(md["key"], filters.map { |fs| + args = fs.split(/\s+/) + Filter.new(args.first, args[1, args.size - 1]) + }) + end + end + end +end + +if false + # basic test + puts Guff::Template.new("test = %{test}, test|h = %{test|h}, test = %{ + test | h | h | u | base64 | hash MD5}").run({ + "test" => "<>&", + }) +end -- cgit v1.2.3